/*
 * Copyright (C) 2015-2017 Alibaba Group Holding Limited
 */

/*
 / _____)             _              | |
( (____  _____ ____ _| |_ _____  ____| |__
 \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 _____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
    (C)2013 Semtech
 ___ _____ _   ___ _  _____ ___  ___  ___ ___
/ __|_   _/_\ / __| |/ / __/ _ \| _ \/ __| __|
\__ \ | |/ _ \ (__| ' <| _| (_) |   / (__| _|
|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___|
embedded.connectivity.solutions===============

Description: LoRa MAC layer implementation

License: Revised BSD License, see LICENSE.TXT file include in the project

Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel
Jaeckle ( STACKFORCE )
*/


#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include "radio.h"
#include "system/timer.h"
#include "LoRaMac.h"
#include "region/Region.h"
#include "LoRaMacCrypto.h"

#include "debug.h"
#include "LoRaMacTest.h"
#include "linkwan.h"

#if defined(CONFIG_LINKWAN) && defined(AOS_COMP_KV)
#include <assert.h>
#include <aos/kv.h>
#endif

/*!
 * Maximum PHY layer payload size
 */
#define LORAMAC_PHY_MAXPAYLOAD 255

/*!
 * Maximum MAC commands buffer size
 */
#define LORA_MAC_COMMAND_MAX_LENGTH 128

/*!
 * Maximum length of the fOpts field
 */
#define LORA_MAC_COMMAND_MAX_FOPTS_LENGTH 15

/*!
 * LoRaMac region.
 */
static LoRaMacRegion_t LoRaMacRegion;

/*!
 * LoRaMac duty cycle for the back-off procedure during the first hour.
 */
#define BACKOFF_DC_1_HOUR 100

/*!
 * LoRaMac duty cycle for the back-off procedure during the next 10 hours.
 */
#define BACKOFF_DC_10_HOURS 1000

/*!
 * LoRaMac duty cycle for the back-off procedure during the next 24 hours.
 */
#define BACKOFF_DC_24_HOURS 10000

/*!
 * Device IEEE EUI
 */
static uint8_t *LoRaMacDevEui;

/*!
 * Application IEEE EUI
 */
static uint8_t *LoRaMacAppEui;

/*!
 * AES encryption/decryption cipher application key
 */
static uint8_t *LoRaMacAppKey;

/*!
 * AES encryption/decryption cipher network session key
 */
static uint8_t LoRaMacNwkSKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                    0x00, 0x00, 0x00, 0x00 };

/*!
 * AES encryption/decryption cipher application session key
 */
static uint8_t LoRaMacAppSKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                    0x00, 0x00, 0x00, 0x00 };

/*!
 * Device nonce is a random value extracted by issuing a sequence of RSSI
 * measurements
 */
static uint16_t LoRaMacDevNonce;

/*!
 * Network ID ( 3 bytes )
 */
static uint32_t LoRaMacNetID;

/*!
 * Mote Address
 */
static uint32_t LoRaMacDevAddr;

/*!
 * Multicast channels linked list
 */
static MulticastParams_t *MulticastChannels = NULL;

/*!
 * Actual device class
 */
static DeviceClass_t LoRaMacDeviceClass;

/*!
 * Indicates if the node is connected to a private or public network
 */
static bool PublicNetwork;

/*!
 * Indicates if the node supports repeaters
 */
static bool RepeaterSupport;

/*!
 * Buffer containing the data to be sent or received.
 */
static uint8_t LoRaMacBuffer[LORAMAC_PHY_MAXPAYLOAD];

/*!
 * Length of packet in LoRaMacBuffer
 */
static uint16_t LoRaMacBufferPktLen = 0;

/*!
 * Length of the payload in LoRaMacBuffer
 */
static uint8_t LoRaMacTxPayloadLen = 0;

/*!
 * Buffer containing the upper layer data.
 */
static uint8_t LoRaMacRxPayload[LORAMAC_PHY_MAXPAYLOAD];

/*!
 * LoRaMAC frame counter. Each time a packet is sent the counter is incremented.
 * Only the 16 LSB bits are sent
 */
static uint32_t UpLinkCounter = 0;

/*!
 * LoRaMAC frame counter. Each time a packet is received the counter is
 * incremented. Only the 16 LSB bits are received
 */
static uint32_t DownLinkCounter = 0;

/*!
 * IsPacketCounterFixed enables the MIC field tests by fixing the
 * UpLinkCounter value
 */
static bool IsUpLinkCounterFixed = false;

/*!
 * Used for test purposes. Disables the opening of the reception windows.
 */
static bool IsRxWindowsEnabled = true;

/*!
 * Indicates if the MAC layer has already joined a network.
 */
static bool IsLoRaMacNetworkJoined = false;

/*!
 * LoRaMac ADR control status
 */
static bool AdrCtrlOn = false;

/*!
 * Counts the number of missed ADR acknowledgements
 */
static uint32_t AdrAckCounter = 0;

/*!
 * If the node has sent a FRAME_TYPE_DATA_CONFIRMED_UP this variable indicates
 * if the nodes needs to manage the server acknowledgement.
 */
static bool NodeAckRequested = false;

/*!
 * If the server has sent a FRAME_TYPE_DATA_CONFIRMED_DOWN this variable
 * indicates if the ACK bit must be set for the next transmission
 */
static bool SrvAckRequested = false;

/*!
 * Indicates if the MAC layer wants to send MAC commands
 */
static bool MacCommandsInNextTx = false;

/*!
 * Contains the current MacCommandsBuffer index
 */
static uint8_t MacCommandsBufferIndex = 0;

/*!
 * Contains the current MacCommandsBuffer index for MAC commands to repeat
 */
static uint8_t MacCommandsBufferToRepeatIndex = 0;

/*!
 * Buffer containing the MAC layer commands
 */
static uint8_t MacCommandsBuffer[LORA_MAC_COMMAND_MAX_LENGTH];

/*!
 * Buffer containing the MAC layer commands which must be repeated
 */
static uint8_t MacCommandsBufferToRepeat[LORA_MAC_COMMAND_MAX_LENGTH];

/*!
 * LoRaMac parameters loaded from flash/eeprom^M
 */
LoRaMacParams_t LoRaMacParamsLoaded;

/*!
 * LoRaMac parameters
 */
LoRaMacParams_t LoRaMacParams;

/*!
 * LoRaMac default parameters
 */
LoRaMacParams_t LoRaMacParamsDefaults;

static bool UseParamsLoaded = false;

/*!
 * Uplink messages repetitions counter
 */
static uint8_t ChannelsNbRepCounter = 0;

/*!
 * Maximum duty cycle
 * \remark Possibility to shutdown the device.
 */
static uint8_t MaxDCycle = 0;

/*!
 * Aggregated duty cycle management
 */
static uint16_t    AggregatedDCycle;
static TimerTime_t AggregatedLastTxDoneTime;
static TimerTime_t AggregatedTimeOff;

/*!
 * Enables/Disables duty cycle management (Test only)
 */
static bool DutyCycleOn;

/*!
 * Current channel index
 */
static uint8_t Channel;

/*!
 * Current channel index
 */
static uint8_t LastTxChannel;

/*!
 * Set to true, if the last uplink was a join request
 */
static bool LastTxIsJoinRequest;

/*!
 * Stores the time at LoRaMac initialization.
 *
 * \remark Used for the BACKOFF_DC computation.
 */
static TimerTime_t LoRaMacInitializationTime = 0;

/*!
 * LoRaMac internal states
 */
enum eLoRaMacState
{
    LORAMAC_IDLE       = 0x00000000,
    LORAMAC_TX_RUNNING = 0x00000001,
    LORAMAC_RX         = 0x00000002,
    LORAMAC_ACK_REQ    = 0x00000004,
    LORAMAC_ACK_RETRY  = 0x00000008,
    LORAMAC_TX_DELAYED = 0x00000010,
    LORAMAC_TX_CONFIG  = 0x00000020,
    LORAMAC_RX_ABORT   = 0x00000040,
};

/*!
 * LoRaMac internal state
 */
uint32_t LoRaMacState = LORAMAC_IDLE;

/*!
 * LoRaMac timer used to check the LoRaMacState (runs every second)
 */
static TimerEvent_t MacStateCheckTimer;

/*!
 * LoRaMac upper layer event functions
 */
static LoRaMacPrimitives_t *LoRaMacPrimitives;

/*!
 * LoRaMac upper layer callback functions
 */
static LoRaMacCallback_t *LoRaMacCallbacks;

/*!
 * Radio events function pointer
 */
static RadioEvents_t RadioEvents;

/*!
 * Export RadioEvents (Test only)
 */
RadioEvents_t *RadioEventsTest = &RadioEvents;

/*!
 * LoRaMac duty cycle delayed Tx timer
 */
static TimerEvent_t TxDelayedTimer;

/*!
 * LoRaMac reception windows timers
 */
static TimerEvent_t RxWindowTimer1;
static TimerEvent_t RxWindowTimer2;

/*!
 * LoRaMac reception windows delay
 * \remark normal frame: RxWindowXDelay = ReceiveDelayX - RADIO_WAKEUP_TIME
 *         join frame  : RxWindowXDelay = JoinAcceptDelayX - RADIO_WAKEUP_TIME
 */
static uint32_t RxWindow1Delay;
static uint32_t RxWindow2Delay;

/*!
 * LoRaMac Rx windows configuration
 */
static RxConfigParams_t RxWindow1Config;
static RxConfigParams_t RxWindow2Config;

/*!
 * Acknowledge timeout timer. Used for packet retransmissions.
 */
static TimerEvent_t AckTimeoutTimer;

/*!
 * Number of trials to get a frame acknowledged
 */
static uint8_t AckTimeoutRetries = 1;

/*!
 * Number of trials to get a frame acknowledged
 */
static uint8_t AckTimeoutRetriesCounter = 1;

/*!
 * Indicates if the AckTimeout timer has expired or not
 */
static bool AckTimeoutRetry = false;

/*!
 * Last transmission time on air
 */
TimerTime_t TxTimeOnAir = 0;

/*!
 * Number of trials for the Join Request
 */
static uint8_t JoinRequestTrials;

/*!
 * Maximum number of trials for the Join Request
 */
static uint8_t MaxJoinRequestTrials;

/*!
 * Structure to hold an MCPS indication data.
 */
static McpsIndication_t McpsIndication;

/*!
 * Structure to hold MCPS confirm data.
 */
static McpsConfirm_t McpsConfirm;

/*!
 * Structure to hold MLME confirm data.
 */
static MlmeConfirm_t MlmeConfirm;

/*!
 * Holds the current rx window slot
 */
static uint8_t RxSlot = 0;

/*!
 * LoRaMac tx/rx operation state
 */
LoRaMacFlags_t LoRaMacFlags;

/*!
 * \brief Function to be executed on Radio Tx Done event
 */
static void OnRadioTxDone(void);

/*!
 * \brief This function prepares the MAC to abort the execution of function
 *        OnRadioRxDone in case of a reception error.
 */
static void PrepareRxDoneAbort(void);

/*!
 * \brief Function to be executed on Radio Rx Done event
 */
static void OnRadioRxDone(uint8_t *payload, uint16_t size, int16_t rssi,
                          int8_t snr);

/*!
 * \brief Function executed on Radio Tx Timeout event
 */
static void OnRadioTxTimeout(void);

/*!
 * \brief Function executed on Radio Rx error event
 */
static void OnRadioRxError(void);

/*!
 * \brief Function executed on Radio Rx Timeout event
 */
static void OnRadioRxTimeout(void);

/*!
 * \brief Function executed on Resend Frame timer event.
 */
static void OnMacStateCheckTimerEvent(void);

/*!
 * \brief Function executed on duty cycle delayed Tx  timer event
 */
static void OnTxDelayedTimerEvent(void);

/*!
 * \brief Function executed on first Rx window timer event
 */
static void OnRxWindow1TimerEvent(void);

/*!
 * \brief Function executed on second Rx window timer event
 */
static void OnRxWindow2TimerEvent(void);

/*!
 * \brief Function executed on AckTimeout timer event
 */
static void OnAckTimeoutTimerEvent(void);

/*!
 * \brief Initializes and opens the reception window
 *
 * \param [IN] rxContinuous Set to true, if the RX is in continuous mode
 * \param [IN] maxRxWindow Maximum RX window timeout
 */
static void RxWindowSetup(bool rxContinuous, uint32_t maxRxWindow);

/*!
 * \brief Adds a new MAC command to be sent.
 *
 * \Remark MAC layer internal function
 *
 * \param [in] cmd MAC command to be added
 *                 [MOTE_MAC_LINK_CHECK_REQ,
 *                  MOTE_MAC_LINK_ADR_ANS,
 *                  MOTE_MAC_DUTY_CYCLE_ANS,
 *                  MOTE_MAC_RX2_PARAM_SET_ANS,
 *                  MOTE_MAC_DEV_STATUS_ANS
 *                  MOTE_MAC_NEW_CHANNEL_ANS]
 * \param [in] p1  1st parameter ( optional depends on the command )
 * \param [in] p2  2nd parameter ( optional depends on the command )
 *
 * \retval status  Function status [0: OK, 1: Unknown command, 2: Buffer full]
 */
static LoRaMacStatus_t AddMacCommand(uint8_t cmd, uint8_t p1, uint8_t p2);

/*!
 * \brief Parses the MAC commands which must be repeated.
 *
 * \Remark MAC layer internal function
 *
 * \param [IN] cmdBufIn  Buffer which stores the MAC commands to send
 * \param [IN] length  Length of the input buffer to parse
 * \param [OUT] cmdBufOut  Buffer which stores the MAC commands which must be
 *                         repeated.
 *
 * \retval Size of the MAC commands to repeat.
 */
static uint8_t ParseMacCommandsToRepeat(uint8_t *cmdBufIn, uint8_t length,
                                        uint8_t *cmdBufOut);

/*!
 * \brief Validates if the payload fits into the frame, taking the datarate
 *        into account.
 *
 * \details Refer to chapter 4.3.2 of the LoRaWAN specification, v1.0
 *
 * \param lenN Length of the application payload. The length depends on the
 *             datarate and is region specific
 *
 * \param datarate Current datarate
 *
 * \param fOptsLen Length of the fOpts field
 *
 * \retval [false: payload does not fit into the frame, true: payload fits into
 *          the frame]
 */
static bool ValidatePayloadLength(uint8_t lenN, int8_t datarate,
                                  uint8_t fOptsLen);

/*!
 * \brief Decodes MAC commands in the fOpts field and in the payload
 */
static void ProcessMacCommands(uint8_t *payload, uint8_t macIndex,
                               uint8_t commandsSize, uint8_t snr);

/*!
 * \brief LoRaMAC layer generic send frame
 *
 * \param [IN] macHdr      MAC header field
 * \param [IN] fPort       MAC payload port
 * \param [IN] fBuffer     MAC data buffer to be sent
 * \param [IN] fBufferSize MAC data buffer size
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t Send(LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer,
                     uint16_t fBufferSize);

/*!
 * \brief LoRaMAC layer frame buffer initialization
 *
 * \param [IN] macHdr      MAC header field
 * \param [IN] fCtrl       MAC frame control field
 * \param [IN] fOpts       MAC commands buffer
 * \param [IN] fPort       MAC payload port
 * \param [IN] fBuffer     MAC data buffer to be sent
 * \param [IN] fBufferSize MAC data buffer size
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t PrepareFrame(LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl,
                             uint8_t fPort, void *fBuffer,
                             uint16_t fBufferSize);

/*
 * \brief Schedules the frame according to the duty cycle
 *
 * \retval Status of the operation
 */
static LoRaMacStatus_t ScheduleTx(void);

/*
 * \brief Calculates the back-off time for the band of a channel.
 *
 * \param [IN] channel     The last Tx channel index
 */
static void CalculateBackOff(uint8_t channel);

/*!
 * \brief LoRaMAC layer prepared frame buffer transmission with channel
 * specification
 *
 * \remark PrepareFrame must be called at least once before calling this
 *         function.
 *
 * \param [IN] channel     Channel to transmit on
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t SendFrameOnChannel(uint8_t channel);

/*!
 * \brief Sets the radio in continuous transmission mode
 *
 * \remark Uses the radio parameters set on the previous transmission.
 *
 * \param [IN] timeout     Time in seconds while the radio is kept in continuous
 * wave mode \retval status          Status of the operation.
 */
LoRaMacStatus_t SetTxContinuousWave(uint16_t timeout);

/*!
 * \brief Sets the radio in continuous transmission mode
 *
 * \remark Uses the radio parameters set on the previous transmission.
 *
 * \param [IN] timeout     Time in seconds while the radio is kept in continuous
 * wave mode \param [IN] frequency   RF frequency to be set. \param [IN] power
 * RF output power to be set. \retval status          Status of the operation.
 */
LoRaMacStatus_t SetTxContinuousWave1(uint16_t timeout, uint32_t frequency,
                                     uint8_t power);

/*!
 * \brief Resets MAC specific parameters to default
 */
static void ResetMacParameters(void);

static void OnRadioTxDone(void)
{
    GetPhyParams_t        getPhy;
    PhyParam_t            phyParam;
    SetBandTxDoneParams_t txDone;
    TimerTime_t           curTime = TimerGetCurrentTime();

    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    } else {
        OnRxWindow2TimerEvent();
    }

    // Setup timers
    if (IsRxWindowsEnabled == true) {
        TimerSetValue(&RxWindowTimer1, RxWindow1Delay);
        TimerStart(&RxWindowTimer1);
        if (LoRaMacDeviceClass != CLASS_C) {
            TimerSetValue(&RxWindowTimer2, RxWindow2Delay);
            TimerStart(&RxWindowTimer2);
        }
        if ((LoRaMacDeviceClass == CLASS_C) || (NodeAckRequested == true)) {
            getPhy.Attribute = PHY_ACK_TIMEOUT;
            phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
            TimerSetValue(&AckTimeoutTimer, RxWindow2Delay + phyParam.Value);
            TimerStart(&AckTimeoutTimer);
        }
    } else {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
        MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT;

        if (LoRaMacFlags.Value == 0) {
            LoRaMacFlags.Bits.McpsReq = 1;
        }
        LoRaMacFlags.Bits.MacDone = 1;
    }

    // Verify if the last uplink was a join request
    if ((LoRaMacFlags.Bits.MlmeReq == 1) &&
        (MlmeConfirm.MlmeRequest == MLME_JOIN)) {
        LastTxIsJoinRequest = true;
    } else {
        LastTxIsJoinRequest = false;
    }

    // Store last Tx channel
    LastTxChannel = Channel;
    // Update last tx done time for the current channel
    txDone.Channel        = Channel;
    txDone.Joined         = IsLoRaMacNetworkJoined;
    txDone.LastTxDoneTime = curTime;
    RegionSetBandTxDone(LoRaMacRegion, &txDone);
    // Update Aggregated last tx done time
    AggregatedLastTxDoneTime = curTime;

    if (NodeAckRequested == false) {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
        ChannelsNbRepCounter++;
    }
}

static void PrepareRxDoneAbort(void)
{
    LoRaMacState |= LORAMAC_RX_ABORT;

    if (NodeAckRequested) {
        OnAckTimeoutTimerEvent();
    }

    LoRaMacFlags.Bits.McpsInd = 1;
    LoRaMacFlags.Bits.MacDone = 1;

    // Trig OnMacCheckTimerEvent call as soon as possible
    TimerSetValue(&MacStateCheckTimer, 1);
    TimerStart(&MacStateCheckTimer);
}

static void OnRadioRxDone(uint8_t *payload, uint16_t size, int16_t rssi,
                          int8_t snr)
{
    LoRaMacHeader_t     macHdr;
    LoRaMacFrameCtrl_t  fCtrl;
    ApplyCFListParams_t applyCFList;
    GetPhyParams_t      getPhy;
    PhyParam_t          phyParam;
    bool                skipIndication = false;

    uint8_t  pktHeaderLen         = 0;
    uint32_t address              = 0;
    uint8_t  appPayloadStartIndex = 0;
    uint8_t  port                 = 0xFF;
    uint8_t  frameLen             = 0;
    uint32_t mic                  = 0;
    uint32_t micRx                = 0;

    uint16_t sequenceCounter     = 0;
    uint16_t sequenceCounterPrev = 0;
    uint16_t sequenceCounterDiff = 0;
    uint32_t downLinkCounter     = 0;

    MulticastParams_t *curMulticastParams = NULL;
    uint8_t           *nwkSKey            = LoRaMacNwkSKey;
    uint8_t           *appSKey            = LoRaMacAppSKey;

    uint8_t multicast = 0;

    bool isMicOk = false;

    McpsConfirm.AckReceived = false;
#ifdef CONFIG_LINKWAN
    MlmeConfirm.Rssi = rssi;
    MlmeConfirm.Snr  = snr;
#endif
    McpsIndication.Rssi            = rssi;
    McpsIndication.Snr             = snr;
    McpsIndication.RxSlot          = RxSlot;
    McpsIndication.Port            = 0;
    McpsIndication.Multicast       = 0;
    McpsIndication.FramePending    = 0;
    McpsIndication.Buffer          = NULL;
    McpsIndication.BufferSize      = 0;
    McpsIndication.RxData          = false;
    McpsIndication.AckReceived     = false;
    McpsIndication.DownLinkCounter = 0;
    McpsIndication.McpsIndication  = MCPS_UNCONFIRMED;

    Radio.Sleep();
    TimerStop(&RxWindowTimer2);

    macHdr.Value = payload[pktHeaderLen++];

    switch (macHdr.Bits.MType) {
        case FRAME_TYPE_JOIN_ACCEPT:
            if (IsLoRaMacNetworkJoined == true) {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
                PrepareRxDoneAbort();
                return;
            }
            LoRaMacJoinDecrypt(payload + 1, size - 1, LoRaMacAppKey,
                               LoRaMacRxPayload + 1);

            LoRaMacRxPayload[0] = macHdr.Value;

            LoRaMacJoinComputeMic(LoRaMacRxPayload, size - LORAMAC_MFR_LEN,
                                  LoRaMacAppKey, &mic);

            micRx |= (uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN];
            micRx |=
              ((uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 1] << 8);
            micRx |=
              ((uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 2] << 16);
            micRx |=
              ((uint32_t)LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 3] << 24);

            if (micRx == mic) {
                LoRaMacJoinComputeSKeys(LoRaMacAppKey, LoRaMacRxPayload + 1,
                                        LoRaMacDevNonce, LoRaMacNwkSKey,
                                        LoRaMacAppSKey);

                LoRaMacNetID = (uint32_t)LoRaMacRxPayload[4];
                LoRaMacNetID |= ((uint32_t)LoRaMacRxPayload[5] << 8);
                LoRaMacNetID |= ((uint32_t)LoRaMacRxPayload[6] << 16);

                LoRaMacDevAddr = (uint32_t)LoRaMacRxPayload[7];
                LoRaMacDevAddr |= ((uint32_t)LoRaMacRxPayload[8] << 8);
                LoRaMacDevAddr |= ((uint32_t)LoRaMacRxPayload[9] << 16);
                LoRaMacDevAddr |= ((uint32_t)LoRaMacRxPayload[10] << 24);

                // DLSettings
                LoRaMacParams.Rx1DrOffset = (LoRaMacRxPayload[11] >> 4) & 0x07;
                LoRaMacParams.Rx2Channel.Datarate = LoRaMacRxPayload[11] & 0x0F;

                // RxDelay
                LoRaMacParams.ReceiveDelay1 = (LoRaMacRxPayload[12] & 0x0F);
                if (LoRaMacParams.ReceiveDelay1 == 0) {
                    LoRaMacParams.ReceiveDelay1 = 1;
                }
                LoRaMacParams.ReceiveDelay1 *= 1000;
                LoRaMacParams.ReceiveDelay2 =
                  LoRaMacParams.ReceiveDelay1 + 1000;

                // Apply CF list
                applyCFList.Payload = &LoRaMacRxPayload[13];
                // Size of the regular payload is 12. Plus 1 byte MHDR and 4
                // bytes MIC
                applyCFList.Size = size - 17;

                RegionApplyCFList(LoRaMacRegion, &applyCFList);

                MlmeConfirm.Status     = LORAMAC_EVENT_INFO_STATUS_OK;
                IsLoRaMacNetworkJoined = true;
                LoRaMacParams.ChannelsDatarate =
                  LoRaMacParamsDefaults.ChannelsDatarate;
            } else {
                MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
            }
            break;
        case FRAME_TYPE_DATA_CONFIRMED_DOWN:
        case FRAME_TYPE_DATA_UNCONFIRMED_DOWN: {
            // Check if the received payload size is valid
            getPhy.UplinkDwellTime = LoRaMacParams.DownlinkDwellTime;
            getPhy.Datarate        = McpsIndication.RxDatarate;
            getPhy.Attribute       = PHY_MAX_PAYLOAD;

            // Get the maximum payload length
            if (RepeaterSupport == true) {
                getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER;
            }
            phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
            if (MAX(0, (int16_t)((int16_t)size -
                                 (int16_t)LORA_MAC_FRMPAYLOAD_OVERHEAD)) >
                phyParam.Value) {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
                PrepareRxDoneAbort();
                return;
            }

            address = payload[pktHeaderLen++];
            address |= ((uint32_t)payload[pktHeaderLen++] << 8);
            address |= ((uint32_t)payload[pktHeaderLen++] << 16);
            address |= ((uint32_t)payload[pktHeaderLen++] << 24);

            if (address != LoRaMacDevAddr) {
                curMulticastParams = MulticastChannels;
                while (curMulticastParams != NULL) {
                    if (address == curMulticastParams->Address) {
                        multicast       = 1;
                        nwkSKey         = curMulticastParams->NwkSKey;
                        appSKey         = curMulticastParams->AppSKey;
                        downLinkCounter = curMulticastParams->DownLinkCounter;
                        break;
                    }
                    curMulticastParams = curMulticastParams->Next;
                }
                if (multicast == 0) {
                    // We are not the destination of this frame.
                    McpsIndication.Status =
                      LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL;
                    PrepareRxDoneAbort();
                    return;
                }
            } else {
                multicast       = 0;
                nwkSKey         = LoRaMacNwkSKey;
                appSKey         = LoRaMacAppSKey;
                downLinkCounter = DownLinkCounter;
            }

            fCtrl.Value = payload[pktHeaderLen++];

            sequenceCounter = (uint16_t)payload[pktHeaderLen++];
            sequenceCounter |= (uint16_t)payload[pktHeaderLen++] << 8;

            appPayloadStartIndex = 8 + fCtrl.Bits.FOptsLen;

            micRx |= (uint32_t)payload[size - LORAMAC_MFR_LEN];
            micRx |= ((uint32_t)payload[size - LORAMAC_MFR_LEN + 1] << 8);
            micRx |= ((uint32_t)payload[size - LORAMAC_MFR_LEN + 2] << 16);
            micRx |= ((uint32_t)payload[size - LORAMAC_MFR_LEN + 3] << 24);

            sequenceCounterPrev = (uint16_t)downLinkCounter;
            sequenceCounterDiff = (sequenceCounter - sequenceCounterPrev);

            if (sequenceCounterDiff < (1 << 15)) {
                downLinkCounter += sequenceCounterDiff;
                LoRaMacComputeMic(payload, size - LORAMAC_MFR_LEN, nwkSKey,
                                  address, DOWN_LINK, downLinkCounter, &mic);
                if (micRx == mic) {
                    isMicOk = true;
                }
            } else {
                // check for sequence roll-over
                uint32_t downLinkCounterTmp =
                  downLinkCounter + 0x10000 + (int16_t)sequenceCounterDiff;
                LoRaMacComputeMic(payload, size - LORAMAC_MFR_LEN, nwkSKey,
                                  address, DOWN_LINK, downLinkCounterTmp, &mic);
                if (micRx == mic) {
                    isMicOk         = true;
                    downLinkCounter = downLinkCounterTmp;
                }
            }

            // Check for a the maximum allowed counter difference
            getPhy.Attribute = PHY_MAX_FCNT_GAP;
            phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
            if (sequenceCounterDiff >= phyParam.Value) {
                McpsIndication.Status =
                  LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOSS;
                McpsIndication.DownLinkCounter = downLinkCounter;
                PrepareRxDoneAbort();
                return;
            }

            if (isMicOk == true) {
                McpsIndication.Status          = LORAMAC_EVENT_INFO_STATUS_OK;
                McpsIndication.Multicast       = multicast;
                McpsIndication.FramePending    = fCtrl.Bits.FPending;
                McpsIndication.Buffer          = NULL;
                McpsIndication.BufferSize      = 0;
                McpsIndication.DownLinkCounter = downLinkCounter;

                McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;

                AdrAckCounter                  = 0;
                MacCommandsBufferToRepeatIndex = 0;

                // Update 32 bits downlink counter
                if (multicast == 1) {
                    McpsIndication.McpsIndication = MCPS_MULTICAST;

                    if ((curMulticastParams->DownLinkCounter ==
                         downLinkCounter) &&
                        (curMulticastParams->DownLinkCounter != 0)) {
                        McpsIndication.Status =
                          LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
                        McpsIndication.DownLinkCounter = downLinkCounter;
                        PrepareRxDoneAbort();
                        return;
                    }
                    curMulticastParams->DownLinkCounter = downLinkCounter;
                } else {
                    if (macHdr.Bits.MType == FRAME_TYPE_DATA_CONFIRMED_DOWN) {
                        SrvAckRequested               = true;
                        McpsIndication.McpsIndication = MCPS_CONFIRMED;

                        if ((DownLinkCounter == downLinkCounter) &&
                            (DownLinkCounter != 0)) {
                            // Duplicated confirmed downlink. Skip indication.
                            // In this case, the MAC layer shall accept the MAC
                            // commands which are included in the downlink
                            // retransmission. It should not provide the same
                            // frame to the application layer again.
                            skipIndication = true;
                        }
                    } else {
                        SrvAckRequested               = false;
                        McpsIndication.McpsIndication = MCPS_UNCONFIRMED;

                        if ((DownLinkCounter == downLinkCounter) &&
                            (DownLinkCounter != 0)) {
                            McpsIndication.Status =
                              LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
                            McpsIndication.DownLinkCounter = downLinkCounter;
                            PrepareRxDoneAbort();
                            return;
                        }
                    }
                    DownLinkCounter = downLinkCounter;
                }

                // This must be done before parsing the payload and the MAC
                // commands. We need to reset the MacCommandsBufferIndex here,
                // since we need to take retransmissions and repetitions into
                // account. Error cases will be handled in function
                // OnMacStateCheckTimerEvent.
                if (McpsConfirm.McpsRequest == MCPS_CONFIRMED) {
                    if (fCtrl.Bits.Ack == 1) {
                        // Reset MacCommandsBufferIndex when we have received an
                        // ACK.
                        MacCommandsBufferIndex = 0;
                    }
                } else {
                    // Reset the variable if we have received any valid frame.
                    MacCommandsBufferIndex = 0;
                }

                // Process payload and MAC commands
                if (((size - 4) - appPayloadStartIndex) > 0) {
                    port     = payload[appPayloadStartIndex++];
                    frameLen = (size - 4) - appPayloadStartIndex;

                    McpsIndication.Port = port;

                    if (port == 0) {
                        // Only allow frames which do not have fOpts
                        if (fCtrl.Bits.FOptsLen == 0) {
                            LoRaMacPayloadDecrypt(
                              payload + appPayloadStartIndex, frameLen, nwkSKey,
                              address, DOWN_LINK, downLinkCounter,
                              LoRaMacRxPayload);

                            // Decode frame payload MAC commands
                            ProcessMacCommands(LoRaMacRxPayload, 0, frameLen,
                                               snr);
                        } else {
                            skipIndication = true;
                        }
                    } else {
                        if (fCtrl.Bits.FOptsLen > 0) {
                            // Decode Options field MAC commands. Omit the
                            // fPort.
                            ProcessMacCommands(payload, 8,
                                               appPayloadStartIndex - 1, snr);
                        }

                        LoRaMacPayloadDecrypt(payload + appPayloadStartIndex,
                                              frameLen, appSKey, address,
                                              DOWN_LINK, downLinkCounter,
                                              LoRaMacRxPayload);

                        if (skipIndication == false) {
                            McpsIndication.Buffer     = LoRaMacRxPayload;
                            McpsIndication.BufferSize = frameLen;
                            McpsIndication.RxData     = true;
                        }
                    }
                } else {
                    if (fCtrl.Bits.FOptsLen > 0) {
                        // Decode Options field MAC commands
                        ProcessMacCommands(payload, 8, appPayloadStartIndex,
                                           snr);
                    }
                }

                if (skipIndication == false) {
                    // Check if the frame is an acknowledgement
                    if (fCtrl.Bits.Ack == 1) {
                        McpsConfirm.AckReceived    = true;
                        McpsIndication.AckReceived = true;

                        // Stop the AckTimeout timer as no more retransmissions
                        // are needed.
                        TimerStop(&AckTimeoutTimer);
                    } else {
                        McpsConfirm.AckReceived = false;

                        if (AckTimeoutRetriesCounter > AckTimeoutRetries) {
                            // Stop the AckTimeout timer as no more
                            // retransmissions are needed.
                            TimerStop(&AckTimeoutTimer);
                        }
                    }
                }
                // Provide always an indication, skip the callback to the user
                // application, in case of a confirmed downlink retransmission.
                LoRaMacFlags.Bits.McpsInd     = 1;
                LoRaMacFlags.Bits.McpsIndSkip = skipIndication;
            } else {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL;

                PrepareRxDoneAbort();
                return;
            }
        } break;
        case FRAME_TYPE_PROPRIETARY: {
            memcpy1(LoRaMacRxPayload, &payload[pktHeaderLen], size);

            McpsIndication.McpsIndication = MCPS_PROPRIETARY;
            McpsIndication.Status         = LORAMAC_EVENT_INFO_STATUS_OK;
            McpsIndication.Buffer         = LoRaMacRxPayload;
            McpsIndication.BufferSize     = size - pktHeaderLen;

            LoRaMacFlags.Bits.McpsInd = 1;
            break;
        }
        default:
            McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
            PrepareRxDoneAbort();
            break;
    }
    LoRaMacFlags.Bits.MacDone = 1;

    // Trig OnMacCheckTimerEvent call as soon as possible
    TimerSetValue(&MacStateCheckTimer, 1);
    TimerStart(&MacStateCheckTimer);
}

static void OnRadioTxTimeout(void)
{
    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    } else {
        OnRxWindow2TimerEvent();
    }

    McpsConfirm.Status        = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT;
    MlmeConfirm.Status        = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT;
    LoRaMacFlags.Bits.MacDone = 1;
}

static void OnRadioRxError(void)
{
    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    } else {
        OnRxWindow2TimerEvent();
    }

    if (RxSlot == 0) {
        if (NodeAckRequested == true) {
            McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_ERROR;
        }
        MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_ERROR;

        if (TimerGetElapsedTime(AggregatedLastTxDoneTime) >= RxWindow2Delay) {
            LoRaMacFlags.Bits.MacDone = 1;
        }
    } else {
        if (NodeAckRequested == true) {
            McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_ERROR;
        }
        MlmeConfirm.Status        = LORAMAC_EVENT_INFO_STATUS_RX2_ERROR;
        LoRaMacFlags.Bits.MacDone = 1;
    }
}

static void OnRadioRxTimeout(void)
{
    if (LoRaMacDeviceClass != CLASS_C) {
        Radio.Sleep();
    } else {
        OnRxWindow2TimerEvent();
    }

    if (RxSlot == 0) {
        if (NodeAckRequested == true) {
            McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT;
        }
        MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT;

        if (TimerGetElapsedTime(AggregatedLastTxDoneTime) >= RxWindow2Delay) {
            LoRaMacFlags.Bits.MacDone = 1;
        }
    } else {
        if (NodeAckRequested == true) {
            McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT;
        }
        MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT;

        if (LoRaMacDeviceClass != CLASS_C) {
            LoRaMacFlags.Bits.MacDone = 1;
        }
    }
}

static void OnMacStateCheckTimerEvent(void)
{
    GetPhyParams_t getPhy;
    PhyParam_t     phyParam;
    bool           txTimeout = false;

    TimerStop(&MacStateCheckTimer);

    if (LoRaMacFlags.Bits.MacDone == 1) {
        if ((LoRaMacState & LORAMAC_RX_ABORT) == LORAMAC_RX_ABORT) {
            LoRaMacState &= ~LORAMAC_RX_ABORT;
            LoRaMacState &= ~LORAMAC_TX_RUNNING;
        }

        if ((LoRaMacFlags.Bits.MlmeReq == 1) ||
            ((LoRaMacFlags.Bits.McpsReq == 1))) {
            if ((McpsConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT) ||
                (MlmeConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT)) {
                // Stop transmit cycle due to tx timeout.
                LoRaMacState &= ~LORAMAC_TX_RUNNING;
                MacCommandsBufferIndex  = 0;
                McpsConfirm.NbRetries   = AckTimeoutRetriesCounter;
                McpsConfirm.AckReceived = false;
                McpsConfirm.TxTimeOnAir = 0;
                txTimeout               = true;
            }
        }

        if ((NodeAckRequested == false) && (txTimeout == false)) {
            if ((LoRaMacFlags.Bits.MlmeReq == 1) ||
                ((LoRaMacFlags.Bits.McpsReq == 1))) {
                if ((LoRaMacFlags.Bits.MlmeReq == 1) &&
                    (MlmeConfirm.MlmeRequest == MLME_JOIN)) {
                    // Procedure for the join request
                    MlmeConfirm.NbRetries = JoinRequestTrials;

                    if (MlmeConfirm.Status == LORAMAC_EVENT_INFO_STATUS_OK) {
                        // Node joined successfully
                        UpLinkCounter        = 0;
                        ChannelsNbRepCounter = 0;
                        LoRaMacState &= ~LORAMAC_TX_RUNNING;
                    } else {
                        if (JoinRequestTrials >= MaxJoinRequestTrials) {
                            LoRaMacState &= ~LORAMAC_TX_RUNNING;
                        } else {
                            LoRaMacFlags.Bits.MacDone = 0;
                            // Sends the same frame again
                            OnTxDelayedTimerEvent();
                        }
                    }
                } else {
                    // Procedure for all other frames
                    if ((ChannelsNbRepCounter >= LoRaMacParams.ChannelsNbRep) ||
                        (LoRaMacFlags.Bits.McpsInd == 1)) {
                        if (LoRaMacFlags.Bits.McpsInd == 0) {
                            // Maximum repetitions without downlink. Reset
                            // MacCommandsBufferIndex. Increase ADR Ack counter.
                            // Only process the case when the MAC did not
                            // receive a downlink.
                            MacCommandsBufferIndex = 0;
                            AdrAckCounter++;
                        }

                        ChannelsNbRepCounter = 0;

                        if (IsUpLinkCounterFixed == false) {
                            UpLinkCounter++;
                        }

                        LoRaMacState &= ~LORAMAC_TX_RUNNING;
                    } else {
                        LoRaMacFlags.Bits.MacDone = 0;
                        // Sends the same frame again
                        OnTxDelayedTimerEvent();
                    }
                }
            }
        }

        if (LoRaMacFlags.Bits.McpsInd == 1) {
            // Procedure if we received a frame
            if ((McpsConfirm.AckReceived == true) ||
                (AckTimeoutRetriesCounter > AckTimeoutRetries)) {
                AckTimeoutRetry  = false;
                NodeAckRequested = false;
                if (IsUpLinkCounterFixed == false) {
                    UpLinkCounter++;
                }
                McpsConfirm.NbRetries = AckTimeoutRetriesCounter;

                LoRaMacState &= ~LORAMAC_TX_RUNNING;
            }
        }

        if ((AckTimeoutRetry == true) &&
            ((LoRaMacState & LORAMAC_TX_DELAYED) == 0)) {
            // Retransmissions procedure for confirmed uplinks
            AckTimeoutRetry = false;
            if ((AckTimeoutRetriesCounter < AckTimeoutRetries) &&
                (AckTimeoutRetriesCounter <= MAX_ACK_RETRIES)) {
                AckTimeoutRetriesCounter++;

                if ((AckTimeoutRetriesCounter % 2) == 1) {
                    getPhy.Attribute       = PHY_NEXT_LOWER_TX_DR;
                    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
                    getPhy.Datarate        = LoRaMacParams.ChannelsDatarate;
                    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
                    LoRaMacParams.ChannelsDatarate = phyParam.Value;
                }
                // Try to send the frame again
                if (ScheduleTx() == LORAMAC_STATUS_OK) {
                    LoRaMacFlags.Bits.MacDone = 0;
                } else {
                    // The DR is not applicable for the payload size
                    McpsConfirm.Status =
                      LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR;

                    MacCommandsBufferIndex = 0;
                    LoRaMacState &= ~LORAMAC_TX_RUNNING;
                    NodeAckRequested        = false;
                    McpsConfirm.AckReceived = false;
                    McpsConfirm.NbRetries   = AckTimeoutRetriesCounter;
                    McpsConfirm.Datarate    = LoRaMacParams.ChannelsDatarate;
                    if (IsUpLinkCounterFixed == false) {
                        UpLinkCounter++;
                    }
                }
            } else {
                RegionInitDefaults(LoRaMacRegion, INIT_TYPE_RESTORE);

                LoRaMacState &= ~LORAMAC_TX_RUNNING;

                MacCommandsBufferIndex  = 0;
                NodeAckRequested        = false;
                McpsConfirm.AckReceived = false;
                McpsConfirm.NbRetries   = AckTimeoutRetriesCounter;
                if (IsUpLinkCounterFixed == false) {
                    UpLinkCounter++;
                }
            }
        }
    }
    // Handle reception for Class B and Class C
    if ((LoRaMacState & LORAMAC_RX) == LORAMAC_RX) {
        LoRaMacState &= ~LORAMAC_RX;
    }
    if (LoRaMacState == LORAMAC_IDLE) {
        if (LoRaMacFlags.Bits.McpsReq == 1) {
            LoRaMacPrimitives->MacMcpsConfirm(&McpsConfirm);
            LoRaMacFlags.Bits.McpsReq = 0;
        }

        if (LoRaMacFlags.Bits.MlmeReq == 1) {
            LoRaMacPrimitives->MacMlmeConfirm(&MlmeConfirm);
            LoRaMacFlags.Bits.MlmeReq = 0;
        }

        // Procedure done. Reset variables.
        LoRaMacFlags.Bits.MacDone = 0;
    } else {
        // Operation not finished restart timer
        TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
        TimerStart(&MacStateCheckTimer);
    }

    if (LoRaMacFlags.Bits.McpsInd == 1) {
        if (LoRaMacDeviceClass == CLASS_C) {
            // Activate RX2 window for Class C
            OnRxWindow2TimerEvent();
        }
        if (LoRaMacFlags.Bits.McpsIndSkip == 0) {
            LoRaMacPrimitives->MacMcpsIndication(&McpsIndication);
        }
        LoRaMacFlags.Bits.McpsIndSkip = 0;
        LoRaMacFlags.Bits.McpsInd     = 0;
    }
}

static void OnTxDelayedTimerEvent(void)
{
    LoRaMacHeader_t     macHdr;
    LoRaMacFrameCtrl_t  fCtrl;
    AlternateDrParams_t altDr;

    TimerStop(&TxDelayedTimer);
    LoRaMacState &= ~LORAMAC_TX_DELAYED;

    if ((LoRaMacFlags.Bits.MlmeReq == 1) &&
        (MlmeConfirm.MlmeRequest == MLME_JOIN)) {
        ResetMacParameters();

        altDr.NbTrials = JoinRequestTrials + 1;
#ifdef CONFIG_LINKWAN
        altDr.joinmethod = LoRaMacParams.method;
        altDr.datarate   = LoRaMacParams.ChannelsDatarate;
#endif
        LoRaMacParams.ChannelsDatarate =
          RegionAlternateDr(LoRaMacRegion, &altDr);

        macHdr.Value      = 0;
        macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ;

        fCtrl.Value    = 0;
        fCtrl.Bits.Adr = AdrCtrlOn;

        /* In case of join request retransmissions, the stack must prepare
         * the frame again, because the network server keeps track of the random
         * LoRaMacDevNonce values to prevent reply attacks. */
        PrepareFrame(&macHdr, &fCtrl, 0, NULL, 0);
    }

    ScheduleTx();
}

static void OnRxWindow1TimerEvent(void)
{
    TimerStop(&RxWindowTimer1);
    RxSlot = 0;

    RxWindow1Config.Channel           = Channel;
    RxWindow1Config.DrOffset          = LoRaMacParams.Rx1DrOffset;
    RxWindow1Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime;
    RxWindow1Config.RepeaterSupport   = RepeaterSupport;
    RxWindow1Config.RxContinuous      = false;
    RxWindow1Config.Window            = RxSlot;

    if (LoRaMacDeviceClass == CLASS_C) {
        Radio.Standby();
    }

    RegionRxConfig(LoRaMacRegion, &RxWindow1Config,
                   (int8_t *)&McpsIndication.RxDatarate);
    RxWindowSetup(RxWindow1Config.RxContinuous, LoRaMacParams.MaxRxWindow);
    DBG_LINKWAN("Rx, Freq %d, DR %d, window 1\r\n", RxWindow1Config.Frequency,
                McpsIndication.RxDatarate);
}

static void OnRxWindow2TimerEvent(void)
{
    TimerStop(&RxWindowTimer2);

    RxWindow2Config.Channel           = Channel;
    RxWindow2Config.Frequency         = LoRaMacParams.Rx2Channel.Frequency;
    RxWindow2Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime;
    RxWindow2Config.RepeaterSupport   = RepeaterSupport;
    RxWindow2Config.Window            = 1;

    if (LoRaMacDeviceClass != CLASS_C) {
        RxWindow2Config.RxContinuous = false;
    } else {
        RxWindow2Config.RxContinuous = true;
    }

    if (RegionRxConfig(LoRaMacRegion, &RxWindow2Config,
                       (int8_t *)&McpsIndication.RxDatarate) == true) {
        RxWindowSetup(RxWindow2Config.RxContinuous, LoRaMacParams.MaxRxWindow);
        RxSlot = RxWindow2Config.Window;
    }
    DBG_LINKWAN("Rx, Freq %d, DR %d, window 2\r\n", RxWindow2Config.Frequency,
                McpsIndication.RxDatarate);
}

static void OnAckTimeoutTimerEvent(void)
{
    TimerStop(&AckTimeoutTimer);

    if (NodeAckRequested == true) {
        AckTimeoutRetry = true;
        LoRaMacState &= ~LORAMAC_ACK_REQ;
    }
    if (LoRaMacDeviceClass == CLASS_C) {
        LoRaMacFlags.Bits.MacDone = 1;
    }
}

static void RxWindowSetup(bool rxContinuous, uint32_t maxRxWindow)
{
    if (rxContinuous == false) {
        Radio.Rx(maxRxWindow);
    } else {
        Radio.Rx(0); // Continuous mode
    }
}

static bool ValidatePayloadLength(uint8_t lenN, int8_t datarate,
                                  uint8_t fOptsLen)
{
    GetPhyParams_t getPhy;
    PhyParam_t     phyParam;
    uint16_t       maxN        = 0;
    uint16_t       payloadSize = 0;

    // Setup PHY request
    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
    getPhy.Datarate        = datarate;
    getPhy.Attribute       = PHY_MAX_PAYLOAD;

    // Get the maximum payload length
    if (RepeaterSupport == true) {
        getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER;
    }
    phyParam = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    maxN     = phyParam.Value;

    // Calculate the resulting payload size
    payloadSize = (lenN + fOptsLen);

    // Validation of the application payload size
    if ((payloadSize <= maxN) && (payloadSize <= LORAMAC_PHY_MAXPAYLOAD)) {
        return true;
    }
    return false;
}

static LoRaMacStatus_t AddMacCommand(uint8_t cmd, uint8_t p1, uint8_t p2)
{
    LoRaMacStatus_t status = LORAMAC_STATUS_BUSY;
    // The maximum buffer length must take MAC commands to re-send into account.
    uint8_t bufLen =
      LORA_MAC_COMMAND_MAX_LENGTH - MacCommandsBufferToRepeatIndex;

    switch (cmd) {
        case MOTE_MAC_LINK_CHECK_REQ:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this command
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_LINK_ADR_ANS:
            if (MacCommandsBufferIndex < (bufLen - 1)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Margin
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status                                      = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DUTY_CYCLE_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_RX_PARAM_SETUP_ANS:
            if (MacCommandsBufferIndex < (bufLen - 1)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Datarate ACK, Channel ACK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status                                      = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DEV_STATUS_ANS:
            if (MacCommandsBufferIndex < (bufLen - 2)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // 1st byte Battery
                // 2nd byte Margin
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                MacCommandsBuffer[MacCommandsBufferIndex++] = p2;
                status                                      = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_NEW_CHANNEL_ANS:
            if (MacCommandsBufferIndex < (bufLen - 1)) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Datarate range OK, Channel frequency OK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status                                      = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_RX_TIMING_SETUP_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_TX_PARAM_SETUP_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DL_CHANNEL_ANS:
            if (MacCommandsBufferIndex < bufLen) {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Uplink frequency exists, Channel frequency OK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status                                      = LORAMAC_STATUS_OK;
            }
            break;
        default:
            return LORAMAC_STATUS_SERVICE_UNKNOWN;
    }
    if (status == LORAMAC_STATUS_OK) {
        MacCommandsInNextTx = true;
        if (SrvAckRequested) {
            tx_lora_mac_req();
        }
    }
    return status;
}

static uint8_t ParseMacCommandsToRepeat(uint8_t *cmdBufIn, uint8_t length,
                                        uint8_t *cmdBufOut)
{
    uint8_t i        = 0;
    uint8_t cmdCount = 0;

    if ((cmdBufIn == NULL) || (cmdBufOut == NULL)) {
        return 0;
    }

    for (i = 0; i < length; i++) {
        switch (cmdBufIn[i]) {
            // STICKY
            case MOTE_MAC_DL_CHANNEL_ANS:
            case MOTE_MAC_RX_PARAM_SETUP_ANS: {
                // 1 byte payload
                cmdBufOut[cmdCount++] = cmdBufIn[i++];
                cmdBufOut[cmdCount++] = cmdBufIn[i];
                break;
            }
            case MOTE_MAC_RX_TIMING_SETUP_ANS: {
                // 0 byte payload
                cmdBufOut[cmdCount++] = cmdBufIn[i];
                break;
            }
            // NON-STICKY
            case MOTE_MAC_DEV_STATUS_ANS: {
                // 2 bytes payload
                i += 2;
                break;
            }
            case MOTE_MAC_LINK_ADR_ANS:
            case MOTE_MAC_NEW_CHANNEL_ANS: {
                // 1 byte payload
                i++;
                break;
            }
            case MOTE_MAC_TX_PARAM_SETUP_ANS:
            case MOTE_MAC_DUTY_CYCLE_ANS:
            case MOTE_MAC_LINK_CHECK_REQ: {
                // 0 byte payload
                break;
            }
            default:
                break;
        }
    }

    return cmdCount;
}

static void ProcessMacCommands(uint8_t *payload, uint8_t macIndex,
                               uint8_t commandsSize, uint8_t snr)
{
    uint8_t status = 0;

    while (macIndex < commandsSize) {
        // Decode Frame MAC commands
        switch (payload[macIndex++]) {
            case SRV_MAC_LINK_CHECK_ANS:
                MlmeConfirm.Status      = LORAMAC_EVENT_INFO_STATUS_OK;
                MlmeConfirm.DemodMargin = payload[macIndex++];
                MlmeConfirm.NbGateways  = payload[macIndex++];
                DBG_LINKWAN("margin %d, gateways %d\r\n",
                            MlmeConfirm.DemodMargin, MlmeConfirm.NbGateways);
                break;
            case SRV_MAC_LINK_ADR_REQ: {
                LinkAdrReqParams_t linkAdrReq;
                int8_t             linkAdrDatarate      = DR_0;
                int8_t             linkAdrTxPower       = TX_POWER_0;
                uint8_t            linkAdrNbRep         = 0;
                uint8_t            linkAdrNbBytesParsed = 0;

                // Fill parameter structure
                linkAdrReq.Payload         = &payload[macIndex - 1];
                linkAdrReq.PayloadSize     = commandsSize - (macIndex - 1);
                linkAdrReq.AdrEnabled      = AdrCtrlOn;
                linkAdrReq.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
                linkAdrReq.CurrentDatarate = LoRaMacParams.ChannelsDatarate;
                linkAdrReq.CurrentTxPower  = LoRaMacParams.ChannelsTxPower;
                linkAdrReq.CurrentNbRep    = LoRaMacParams.ChannelsNbRep;

                // Process the ADR requests
                status = RegionLinkAdrReq(LoRaMacRegion, &linkAdrReq,
                                          &linkAdrDatarate, &linkAdrTxPower,
                                          &linkAdrNbRep, &linkAdrNbBytesParsed);
                if ((status & 0x07) == 0x07) {
                    LoRaMacParams.ChannelsDatarate = linkAdrDatarate;
                    LoRaMacParams.ChannelsTxPower  = linkAdrTxPower;
                    LoRaMacParams.ChannelsNbRep    = linkAdrNbRep;
                }

                // Add the answers to the buffer
                for (uint8_t i = 0; i < (linkAdrNbBytesParsed / 5); i++) {
                    AddMacCommand(MOTE_MAC_LINK_ADR_ANS, status, 0);
                }
                // Update MAC index
                macIndex += linkAdrNbBytesParsed - 1;
            } break;
            case SRV_MAC_DUTY_CYCLE_REQ:
                MaxDCycle        = payload[macIndex++];
                AggregatedDCycle = 1 << MaxDCycle;
                AddMacCommand(MOTE_MAC_DUTY_CYCLE_ANS, 0, 0);
                break;
            case SRV_MAC_RX_PARAM_SETUP_REQ: {
                RxParamSetupReqParams_t rxParamSetupReq;
                status = 0x07;

                rxParamSetupReq.DrOffset = (payload[macIndex] >> 4) & 0x07;
                rxParamSetupReq.Datarate = payload[macIndex] & 0x0F;
                macIndex++;

                rxParamSetupReq.Frequency = (uint32_t)payload[macIndex++];
                rxParamSetupReq.Frequency |= (uint32_t)payload[macIndex++] << 8;
                rxParamSetupReq.Frequency |= (uint32_t)payload[macIndex++]
                                             << 16;
                rxParamSetupReq.Frequency *= 100;

                // Perform request on region
                status = RegionRxParamSetupReq(LoRaMacRegion, &rxParamSetupReq);

                if ((status & 0x07) == 0x07) {
                    LoRaMacParams.Rx2Channel.Datarate =
                      rxParamSetupReq.Datarate;
                    LoRaMacParams.Rx2Channel.Frequency =
                      rxParamSetupReq.Frequency;
                    LoRaMacParams.Rx1DrOffset = rxParamSetupReq.DrOffset;
                }
                AddMacCommand(MOTE_MAC_RX_PARAM_SETUP_ANS, status, 0);
            } break;
            case SRV_MAC_DEV_STATUS_REQ: {
                uint8_t batteryLevel = BAT_LEVEL_NO_MEASURE;
                if ((LoRaMacCallbacks != NULL) &&
                    (LoRaMacCallbacks->GetBatteryLevel != NULL)) {
                    batteryLevel = LoRaMacCallbacks->GetBatteryLevel();
                }
                AddMacCommand(MOTE_MAC_DEV_STATUS_ANS, batteryLevel, snr);
                break;
            }
            case SRV_MAC_NEW_CHANNEL_REQ: {
                NewChannelReqParams_t newChannelReq;
                ChannelParams_t       chParam;
                status = 0x03;

                newChannelReq.ChannelId  = payload[macIndex++];
                newChannelReq.NewChannel = &chParam;

                chParam.Frequency = (uint32_t)payload[macIndex++];
                chParam.Frequency |= (uint32_t)payload[macIndex++] << 8;
                chParam.Frequency |= (uint32_t)payload[macIndex++] << 16;
                chParam.Frequency *= 100;
                chParam.Rx1Frequency  = 0;
                chParam.DrRange.Value = payload[macIndex++];

                status = RegionNewChannelReq(LoRaMacRegion, &newChannelReq);

                AddMacCommand(MOTE_MAC_NEW_CHANNEL_ANS, status, 0);
            } break;
            case SRV_MAC_RX_TIMING_SETUP_REQ: {
                uint8_t delay = payload[macIndex++] & 0x0F;

                if (delay == 0) {
                    delay++;
                }
                LoRaMacParams.ReceiveDelay1 = delay * 1000;
                LoRaMacParams.ReceiveDelay2 =
                  LoRaMacParams.ReceiveDelay1 + 1000;
                AddMacCommand(MOTE_MAC_RX_TIMING_SETUP_ANS, 0, 0);
            } break;
            case SRV_MAC_TX_PARAM_SETUP_REQ: {
                TxParamSetupReqParams_t txParamSetupReq;
                uint8_t                 eirpDwellTime = payload[macIndex++];

                txParamSetupReq.UplinkDwellTime   = 0;
                txParamSetupReq.DownlinkDwellTime = 0;

                if ((eirpDwellTime & 0x20) == 0x20) {
                    txParamSetupReq.DownlinkDwellTime = 1;
                }
                if ((eirpDwellTime & 0x10) == 0x10) {
                    txParamSetupReq.UplinkDwellTime = 1;
                }
                txParamSetupReq.MaxEirp = eirpDwellTime & 0x0F;

                // Check the status for correctness
                if (RegionTxParamSetupReq(LoRaMacRegion, &txParamSetupReq) !=
                    -1) {
                    // Accept command
                    LoRaMacParams.UplinkDwellTime =
                      txParamSetupReq.UplinkDwellTime;
                    LoRaMacParams.DownlinkDwellTime =
                      txParamSetupReq.DownlinkDwellTime;
                    LoRaMacParams.MaxEirp =
                      LoRaMacMaxEirpTable[txParamSetupReq.MaxEirp];
                    // Add command response
                    AddMacCommand(MOTE_MAC_TX_PARAM_SETUP_ANS, 0, 0);
                }
            } break;
            case SRV_MAC_DL_CHANNEL_REQ: {
                DlChannelReqParams_t dlChannelReq;
                status = 0x03;

                dlChannelReq.ChannelId    = payload[macIndex++];
                dlChannelReq.Rx1Frequency = (uint32_t)payload[macIndex++];
                dlChannelReq.Rx1Frequency |= (uint32_t)payload[macIndex++] << 8;
                dlChannelReq.Rx1Frequency |= (uint32_t)payload[macIndex++]
                                             << 16;
                dlChannelReq.Rx1Frequency *= 100;

                status = RegionDlChannelReq(LoRaMacRegion, &dlChannelReq);

                AddMacCommand(MOTE_MAC_DL_CHANNEL_ANS, status, 0);
            } break;
            default:
                // Unknown command. ABORT MAC commands processing
                return;
        }
    }
}

LoRaMacStatus_t Send(LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer,
                     uint16_t fBufferSize)
{
    LoRaMacFrameCtrl_t fCtrl;
    LoRaMacStatus_t    status = LORAMAC_STATUS_PARAMETER_INVALID;

    fCtrl.Value          = 0;
    fCtrl.Bits.FOptsLen  = 0;
    fCtrl.Bits.FPending  = 0;
    fCtrl.Bits.Ack       = false;
    fCtrl.Bits.AdrAckReq = false;
    fCtrl.Bits.Adr       = AdrCtrlOn;

    // Prepare the frame
    status = PrepareFrame(macHdr, &fCtrl, fPort, fBuffer, fBufferSize);

    // Validate status
    if (status != LORAMAC_STATUS_OK) {
        return status;
    }

    // Reset confirm parameters
    McpsConfirm.NbRetries     = 0;
    McpsConfirm.AckReceived   = false;
    McpsConfirm.UpLinkCounter = UpLinkCounter;

    status = ScheduleTx();

    return status;
}

static LoRaMacStatus_t ScheduleTx(void)
{
    TimerTime_t      dutyCycleTimeOff = 0;
    NextChanParams_t nextChan;

    // Check if the device is off
    if (MaxDCycle == 255) {
        return LORAMAC_STATUS_DEVICE_OFF;
    }
    if (MaxDCycle == 0) {
        AggregatedTimeOff = 0;
    }

    // Update Backoff
    CalculateBackOff(LastTxChannel);

    nextChan.AggrTimeOff      = AggregatedTimeOff;
    nextChan.Datarate         = LoRaMacParams.ChannelsDatarate;
    nextChan.DutyCycleEnabled = DutyCycleOn;
    nextChan.Joined           = IsLoRaMacNetworkJoined;
    nextChan.LastAggrTx       = AggregatedLastTxDoneTime;
#ifdef CONFIG_LINKWAN
    nextChan.joinmethod      = LoRaMacParams.method;
    nextChan.freqband        = LoRaMacParams.freqband;
    nextChan.update_freqband = LoRaMacParams.update_freqband;
#endif

    // Select channel
    while (RegionNextChannel(LoRaMacRegion, &nextChan, &Channel,
                             &dutyCycleTimeOff, &AggregatedTimeOff) == false) {
        // Set the default datarate
        LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate;
        // Update datarate in the function parameters
        nextChan.Datarate = LoRaMacParams.ChannelsDatarate;
    }

#ifdef CONFIG_LINKWAN
    LoRaMacParams.freqband        = nextChan.freqband;
    LoRaMacParams.update_freqband = nextChan.update_freqband;
#endif

    // Compute Rx1 windows parameters
    RegionComputeRxWindowParameters(
      LoRaMacRegion,
      RegionApplyDrOffset(LoRaMacRegion, LoRaMacParams.DownlinkDwellTime,
                          LoRaMacParams.ChannelsDatarate,
                          LoRaMacParams.Rx1DrOffset),
      LoRaMacParams.MinRxSymbols, LoRaMacParams.SystemMaxRxError,
      &RxWindow1Config);
    // Compute Rx2 windows parameters
    RegionComputeRxWindowParameters(
      LoRaMacRegion, LoRaMacParams.Rx2Channel.Datarate,
      LoRaMacParams.MinRxSymbols, LoRaMacParams.SystemMaxRxError,
      &RxWindow2Config);

    if (IsLoRaMacNetworkJoined == false) {
        RxWindow1Delay =
          LoRaMacParams.JoinAcceptDelay1 + RxWindow1Config.WindowOffset;
        RxWindow2Delay =
          LoRaMacParams.JoinAcceptDelay2 + RxWindow2Config.WindowOffset;
    } else {
        if (ValidatePayloadLength(LoRaMacTxPayloadLen,
                                  LoRaMacParams.ChannelsDatarate,
                                  MacCommandsBufferIndex) == false) {
            return LORAMAC_STATUS_LENGTH_ERROR;
        }
        RxWindow1Delay =
          LoRaMacParams.ReceiveDelay1 + RxWindow1Config.WindowOffset;
        RxWindow2Delay =
          LoRaMacParams.ReceiveDelay2 + RxWindow2Config.WindowOffset;
    }

    // Schedule transmission of frame
    if (dutyCycleTimeOff == 0) {
        // Try to send now
        return SendFrameOnChannel(Channel);
    } else {
        // Send later - prepare timer
        LoRaMacState |= LORAMAC_TX_DELAYED;
        TimerSetValue(&TxDelayedTimer, dutyCycleTimeOff);
        TimerStart(&TxDelayedTimer);

        return LORAMAC_STATUS_OK;
    }
}

static void CalculateBackOff(uint8_t channel)
{
    CalcBackOffParams_t calcBackOff;

    calcBackOff.Joined           = IsLoRaMacNetworkJoined;
    calcBackOff.DutyCycleEnabled = DutyCycleOn;
    calcBackOff.Channel          = channel;
    calcBackOff.ElapsedTime = TimerGetElapsedTime(LoRaMacInitializationTime);
    calcBackOff.TxTimeOnAir = TxTimeOnAir;
    calcBackOff.LastTxIsJoinRequest = LastTxIsJoinRequest;

    // Update regional back-off
    RegionCalcBackOff(LoRaMacRegion, &calcBackOff);

    // Update aggregated time-off
    AggregatedTimeOff =
      AggregatedTimeOff + (TxTimeOnAir * AggregatedDCycle - TxTimeOnAir);
}

static void ResetMacParameters(void)
{
    IsLoRaMacNetworkJoined = false;

    // Counters
    UpLinkCounter   = 0;
    DownLinkCounter = 0;
    AdrAckCounter   = 0;

    ChannelsNbRepCounter = 0;

    AckTimeoutRetries        = 1;
    AckTimeoutRetriesCounter = 1;
    AckTimeoutRetry          = false;

    MaxDCycle        = 0;
    AggregatedDCycle = 1;

    MacCommandsBufferIndex         = 0;
    MacCommandsBufferToRepeatIndex = 0;

    IsRxWindowsEnabled = true;

    LoRaMacParams.ChannelsTxPower   = LoRaMacParamsDefaults.ChannelsTxPower;
    LoRaMacParams.ChannelsDatarate  = LoRaMacParamsDefaults.ChannelsDatarate;
    LoRaMacParams.Rx1DrOffset       = LoRaMacParamsDefaults.Rx1DrOffset;
    LoRaMacParams.Rx2Channel        = LoRaMacParamsDefaults.Rx2Channel;
    LoRaMacParams.UplinkDwellTime   = LoRaMacParamsDefaults.UplinkDwellTime;
    LoRaMacParams.DownlinkDwellTime = LoRaMacParamsDefaults.DownlinkDwellTime;
    LoRaMacParams.MaxEirp           = LoRaMacParamsDefaults.MaxEirp;
    LoRaMacParams.AntennaGain       = LoRaMacParamsDefaults.AntennaGain;

    LoRaMacParams_t *param =
      UseParamsLoaded ? &LoRaMacParamsLoaded : &LoRaMacParamsDefaults;

    LoRaMacParams.ChannelsTxPower   = param->ChannelsTxPower;
    LoRaMacParams.ChannelsDatarate  = param->ChannelsDatarate;
    LoRaMacParams.Rx1DrOffset       = param->Rx1DrOffset;
    LoRaMacParams.Rx2Channel        = param->Rx2Channel;
    LoRaMacParams.UplinkDwellTime   = param->UplinkDwellTime;
    LoRaMacParams.DownlinkDwellTime = param->DownlinkDwellTime;
    LoRaMacParams.MaxEirp           = param->MaxEirp;
    LoRaMacParams.AntennaGain       = param->AntennaGain;

    NodeAckRequested    = false;
    SrvAckRequested     = false;
    MacCommandsInNextTx = false;

    // Reset Multicast downlink counters
    MulticastParams_t *cur = MulticastChannels;
    while (cur != NULL) {
        cur->DownLinkCounter = 0;
        cur                  = cur->Next;
    }

    // Initialize channel index.
    Channel       = 0;
    LastTxChannel = Channel;
}

LoRaMacStatus_t PrepareFrame(LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl,
                             uint8_t fPort, void *fBuffer, uint16_t fBufferSize)
{
    AdrNextParams_t adrNext;
    uint16_t        i;
    uint8_t         pktHeaderLen = 0;
    uint32_t        mic          = 0;
    const void     *payload      = fBuffer;
    uint8_t         framePort    = fPort;

    LoRaMacBufferPktLen = 0;

    NodeAckRequested = false;

    if (fBuffer == NULL) {
        fBufferSize = 0;
    }

    LoRaMacTxPayloadLen = fBufferSize;

    LoRaMacBuffer[pktHeaderLen++] = macHdr->Value;

    switch (macHdr->Bits.MType) {
        case FRAME_TYPE_JOIN_REQ:
            LoRaMacBufferPktLen = pktHeaderLen;

            memcpyr(LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacAppEui, 8);
            LoRaMacBufferPktLen += 8;
            memcpyr(LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacDevEui, 8);
            LoRaMacBufferPktLen += 8;

            LoRaMacDevNonce = Radio.Random();

            LoRaMacBuffer[LoRaMacBufferPktLen++] = LoRaMacDevNonce & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] =
              (LoRaMacDevNonce >> 8) & 0xFF;

            LoRaMacJoinComputeMic(LoRaMacBuffer, LoRaMacBufferPktLen & 0xFF,
                                  LoRaMacAppKey, &mic);

            LoRaMacBuffer[LoRaMacBufferPktLen++] = mic & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = (mic >> 8) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = (mic >> 16) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = (mic >> 24) & 0xFF;

            break;
        case FRAME_TYPE_DATA_CONFIRMED_UP:
            NodeAckRequested = true;
        // Intentional fallthrough
        case FRAME_TYPE_DATA_UNCONFIRMED_UP:
            if (IsLoRaMacNetworkJoined == false) {
                return LORAMAC_STATUS_NO_NETWORK_JOINED; // No network has been
                                                         // joined yet
            }

            // Adr next request
            adrNext.UpdateChanMask  = true;
            adrNext.AdrEnabled      = fCtrl->Bits.Adr;
            adrNext.AdrAckCounter   = AdrAckCounter;
            adrNext.Datarate        = LoRaMacParams.ChannelsDatarate;
            adrNext.TxPower         = LoRaMacParams.ChannelsTxPower;
            adrNext.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;

            fCtrl->Bits.AdrAckReq = RegionAdrNext(
              LoRaMacRegion, &adrNext, &LoRaMacParams.ChannelsDatarate,
              &LoRaMacParams.ChannelsTxPower, &AdrAckCounter);

            if (SrvAckRequested == true) {
                SrvAckRequested = false;
                fCtrl->Bits.Ack = 1;
            }

            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr)&0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr >> 8) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr >> 16) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (LoRaMacDevAddr >> 24) & 0xFF;

            LoRaMacBuffer[pktHeaderLen++] = fCtrl->Value;

            LoRaMacBuffer[pktHeaderLen++] = UpLinkCounter & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = (UpLinkCounter >> 8) & 0xFF;

            // Copy the MAC commands which must be re-send into the MAC command
            // buffer
            memcpy1(&MacCommandsBuffer[MacCommandsBufferIndex],
                    MacCommandsBufferToRepeat, MacCommandsBufferToRepeatIndex);
            MacCommandsBufferIndex += MacCommandsBufferToRepeatIndex;

            if ((payload != NULL) && (LoRaMacTxPayloadLen > 0)) {
                if (MacCommandsInNextTx == true) {
                    if (MacCommandsBufferIndex <=
                        LORA_MAC_COMMAND_MAX_FOPTS_LENGTH) {
                        fCtrl->Bits.FOptsLen += MacCommandsBufferIndex;

                        // Update FCtrl field with new value of OptionsLength
                        LoRaMacBuffer[0x05] = fCtrl->Value;
                        for (i = 0; i < MacCommandsBufferIndex; i++) {
                            LoRaMacBuffer[pktHeaderLen++] =
                              MacCommandsBuffer[i];
                        }
                    } else {
                        LoRaMacTxPayloadLen = MacCommandsBufferIndex;
                        payload             = MacCommandsBuffer;
                        framePort           = 0;
                    }
                }
            } else {
                if ((MacCommandsBufferIndex > 0) &&
                    (MacCommandsInNextTx == true)) {
                    LoRaMacTxPayloadLen = MacCommandsBufferIndex;
                    payload             = MacCommandsBuffer;
                    framePort           = 0;
                }
            }
            MacCommandsInNextTx = false;
            // Store MAC commands which must be re-send in case the device does
            // not receive a downlink anymore
            MacCommandsBufferToRepeatIndex = ParseMacCommandsToRepeat(
              MacCommandsBuffer, MacCommandsBufferIndex,
              MacCommandsBufferToRepeat);
            if (MacCommandsBufferToRepeatIndex > 0) {
                MacCommandsInNextTx = true;
            }

            if ((payload != NULL) && (LoRaMacTxPayloadLen > 0)) {
                LoRaMacBuffer[pktHeaderLen++] = framePort;

                if (framePort == 0) {
                    // Reset buffer index as the mac commands are being sent on
                    // port 0
                    MacCommandsBufferIndex = 0;
                    LoRaMacPayloadEncrypt(
                      (uint8_t *)payload, LoRaMacTxPayloadLen, LoRaMacNwkSKey,
                      LoRaMacDevAddr, UP_LINK, UpLinkCounter,
                      &LoRaMacBuffer[pktHeaderLen]);
                } else {
                    LoRaMacPayloadEncrypt(
                      (uint8_t *)payload, LoRaMacTxPayloadLen, LoRaMacAppSKey,
                      LoRaMacDevAddr, UP_LINK, UpLinkCounter,
                      &LoRaMacBuffer[pktHeaderLen]);
                }
            }
            LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen;

            LoRaMacComputeMic(LoRaMacBuffer, LoRaMacBufferPktLen,
                              LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK,
                              UpLinkCounter, &mic);

            LoRaMacBuffer[LoRaMacBufferPktLen + 0] = mic & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 1] = (mic >> 8) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 2] = (mic >> 16) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 3] = (mic >> 24) & 0xFF;

            LoRaMacBufferPktLen += LORAMAC_MFR_LEN;

            break;
        case FRAME_TYPE_PROPRIETARY:
            if ((fBuffer != NULL) && (LoRaMacTxPayloadLen > 0)) {
                memcpy1(LoRaMacBuffer + pktHeaderLen, (uint8_t *)fBuffer,
                        LoRaMacTxPayloadLen);
                LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen;
            }
            break;
        default:
            return LORAMAC_STATUS_SERVICE_UNKNOWN;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SendFrameOnChannel(uint8_t channel)
{
    TxConfigParams_t txConfig;
    int8_t           txPower = 0;

    txConfig.Channel     = channel;
    txConfig.Datarate    = LoRaMacParams.ChannelsDatarate;
    txConfig.TxPower     = LoRaMacParams.ChannelsTxPower;
    txConfig.MaxEirp     = LoRaMacParams.MaxEirp;
    txConfig.AntennaGain = LoRaMacParams.AntennaGain;
    txConfig.PktLen      = LoRaMacBufferPktLen;

    RegionTxConfig(LoRaMacRegion, &txConfig, &txPower, &TxTimeOnAir);

    MlmeConfirm.Status   = LORAMAC_EVENT_INFO_STATUS_ERROR;
    McpsConfirm.Status   = LORAMAC_EVENT_INFO_STATUS_ERROR;
    McpsConfirm.Datarate = LoRaMacParams.ChannelsDatarate;
    McpsConfirm.TxPower  = txPower;

    // Store the time on air
    McpsConfirm.TxTimeOnAir = TxTimeOnAir;
    MlmeConfirm.TxTimeOnAir = TxTimeOnAir;

    // Starts the MAC layer status check timer
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
    TimerStart(&MacStateCheckTimer);

    if (IsLoRaMacNetworkJoined == false) {
        JoinRequestTrials++;
    }

    // Send now
    Radio.Send(LoRaMacBuffer, LoRaMacBufferPktLen);

    LoRaMacState |= LORAMAC_TX_RUNNING;

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SetTxContinuousWave(uint16_t timeout)
{
    ContinuousWaveParams_t continuousWave;

    continuousWave.Channel     = Channel;
    continuousWave.Datarate    = LoRaMacParams.ChannelsDatarate;
    continuousWave.TxPower     = LoRaMacParams.ChannelsTxPower;
    continuousWave.MaxEirp     = LoRaMacParams.MaxEirp;
    continuousWave.AntennaGain = LoRaMacParams.AntennaGain;
    continuousWave.Timeout     = timeout;

    RegionSetContinuousWave(LoRaMacRegion, &continuousWave);

    // Starts the MAC layer status check timer
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
    TimerStart(&MacStateCheckTimer);

    LoRaMacState |= LORAMAC_TX_RUNNING;

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SetTxContinuousWave1(uint16_t timeout, uint32_t frequency,
                                     uint8_t power)
{
    Radio.SetTxContinuousWave(frequency, power, timeout);

    // Starts the MAC layer status check timer
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);
    TimerStart(&MacStateCheckTimer);

    LoRaMacState |= LORAMAC_TX_RUNNING;

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacInitialization(LoRaMacPrimitives_t *primitives,
                                      LoRaMacCallback_t   *callbacks,
                                      LoRaMacRegion_t      region)
{
    GetPhyParams_t getPhy;
    PhyParam_t     phyParam;

    if (primitives == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    if ((primitives->MacMcpsConfirm == NULL) ||
        (primitives->MacMcpsIndication == NULL) ||
        (primitives->MacMlmeConfirm == NULL)) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    // Verify if the region is supported
    if (RegionIsActive(region) == false) {
        return LORAMAC_STATUS_REGION_NOT_SUPPORTED;
    }

    LoRaMacPrimitives = primitives;
    LoRaMacCallbacks  = callbacks;
    LoRaMacRegion     = region;

    LoRaMacFlags.Value = 0;

    LoRaMacDeviceClass = CLASS_A;
    LoRaMacState       = LORAMAC_IDLE;

    JoinRequestTrials    = 0;
    MaxJoinRequestTrials = 1;
    RepeaterSupport      = false;

    // Reset duty cycle times
    AggregatedLastTxDoneTime = 0;
    AggregatedTimeOff        = 0;

    // Reset to defaults
    getPhy.Attribute = PHY_DUTY_CYCLE;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    DutyCycleOn      = (bool)phyParam.Value;

    getPhy.Attribute = PHY_DEF_TX_POWER;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ChannelsTxPower = phyParam.Value;

    getPhy.Attribute = PHY_DEF_TX_DR;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ChannelsDatarate = phyParam.Value;

    getPhy.Attribute = PHY_MAX_RX_WINDOW;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.MaxRxWindow = phyParam.Value;

    getPhy.Attribute = PHY_RECEIVE_DELAY1;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ReceiveDelay1 = phyParam.Value;

    getPhy.Attribute = PHY_RECEIVE_DELAY2;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.ReceiveDelay2 = phyParam.Value;

    getPhy.Attribute = PHY_JOIN_ACCEPT_DELAY1;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.JoinAcceptDelay1 = phyParam.Value;

    getPhy.Attribute = PHY_JOIN_ACCEPT_DELAY2;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.JoinAcceptDelay2 = phyParam.Value;

    getPhy.Attribute = PHY_DEF_DR1_OFFSET;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.Rx1DrOffset = phyParam.Value;

    getPhy.Attribute = PHY_DEF_RX2_FREQUENCY;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.Rx2Channel.Frequency = phyParam.Value;

    getPhy.Attribute = PHY_DEF_RX2_DR;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.Rx2Channel.Datarate = phyParam.Value;

    getPhy.Attribute = PHY_DEF_UPLINK_DWELL_TIME;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.UplinkDwellTime = phyParam.Value;

    getPhy.Attribute = PHY_DEF_DOWNLINK_DWELL_TIME;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.DownlinkDwellTime = phyParam.Value;

    getPhy.Attribute              = PHY_DEF_MAX_EIRP;
    phyParam                      = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.MaxEirp = phyParam.fValue;

    getPhy.Attribute = PHY_DEF_ANTENNA_GAIN;
    phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    LoRaMacParamsDefaults.AntennaGain = phyParam.fValue;

    RegionInitDefaults(LoRaMacRegion, INIT_TYPE_INIT);

    // Init parameters which are not set in function ResetMacParameters
    LoRaMacParamsDefaults.ChannelsNbRep    = 1;
    LoRaMacParamsDefaults.SystemMaxRxError = 10;
    LoRaMacParamsDefaults.MinRxSymbols     = 6;

    LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError;
    LoRaMacParams.MinRxSymbols     = LoRaMacParamsDefaults.MinRxSymbols;
    LoRaMacParams.MaxRxWindow      = LoRaMacParamsDefaults.MaxRxWindow;
    LoRaMacParams.ReceiveDelay1    = LoRaMacParamsDefaults.ReceiveDelay1;
    LoRaMacParams.ReceiveDelay2    = LoRaMacParamsDefaults.ReceiveDelay2;
    LoRaMacParams.JoinAcceptDelay1 = LoRaMacParamsDefaults.JoinAcceptDelay1;
    LoRaMacParams.JoinAcceptDelay2 = LoRaMacParamsDefaults.JoinAcceptDelay2;
    LoRaMacParams.ChannelsNbRep    = LoRaMacParamsDefaults.ChannelsNbRep;

    ResetMacParameters();

#if defined(CONFIG_LINKWAN) && defined(AOS_COMP_KV)
    uint32_t len = sizeof(LoRaMacParamsLoaded);
    if (aos_kv_get("gLoRaMacPara", &LoRaMacParamsLoaded, &len) == 0) {
        memcpy(&LoRaMacParams, &LoRaMacParamsLoaded,
               sizeof(LoRaMacParamsLoaded));
        UseParamsLoaded = true;
    }
#endif

    // Initialize timers
    TimerInit(&MacStateCheckTimer, OnMacStateCheckTimerEvent);
    TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT);

    TimerInit(&TxDelayedTimer, OnTxDelayedTimerEvent);
    TimerInit(&RxWindowTimer1, OnRxWindow1TimerEvent);
    TimerInit(&RxWindowTimer2, OnRxWindow2TimerEvent);
    TimerInit(&AckTimeoutTimer, OnAckTimeoutTimerEvent);

    // Store the current initialization time
    LoRaMacInitializationTime = TimerGetCurrentTime();

    // Initialize Radio driver
    RadioEvents.TxDone    = OnRadioTxDone;
    RadioEvents.RxDone    = OnRadioRxDone;
    RadioEvents.RxError   = OnRadioRxError;
    RadioEvents.TxTimeout = OnRadioTxTimeout;
    RadioEvents.RxTimeout = OnRadioRxTimeout;
    Radio.Init(&RadioEvents);

    // Random seed initialization
    srand1(Radio.Random());

    PublicNetwork = true;
    Radio.SetPublicNetwork(PublicNetwork);
    Radio.Sleep();

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacQueryTxPossible(uint8_t size, LoRaMacTxInfo_t *txInfo)
{
    AdrNextParams_t adrNext;
    GetPhyParams_t  getPhy;
    PhyParam_t      phyParam;
    int8_t          datarate = LoRaMacParamsDefaults.ChannelsDatarate;
    int8_t          txPower  = LoRaMacParamsDefaults.ChannelsTxPower;
    uint8_t fOptLen = MacCommandsBufferIndex + MacCommandsBufferToRepeatIndex;

    if (txInfo == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    // Setup ADR request
    adrNext.UpdateChanMask  = false;
    adrNext.AdrEnabled      = AdrCtrlOn;
    adrNext.AdrAckCounter   = AdrAckCounter;
    adrNext.Datarate        = LoRaMacParams.ChannelsDatarate;
    adrNext.TxPower         = LoRaMacParams.ChannelsTxPower;
    adrNext.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;

    // We call the function for information purposes only. We don't want to
    // apply the datarate, the tx power and the ADR ack counter.
    RegionAdrNext(LoRaMacRegion, &adrNext, &datarate, &txPower, &AdrAckCounter);

    // Setup PHY request
    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
    getPhy.Datarate        = datarate;
    getPhy.Attribute       = PHY_MAX_PAYLOAD;

    // Change request in case repeater is supported
    if (RepeaterSupport == true) {
        getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER;
    }
    phyParam                   = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    txInfo->CurrentPayloadSize = phyParam.Value;

    // Verify if the fOpts fit into the maximum payload
    if (txInfo->CurrentPayloadSize >= fOptLen) {
        txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize - fOptLen;
    } else {
        txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize;
        // The fOpts don't fit into the maximum payload. Omit the MAC commands
        // to ensure that another uplink is possible.
        fOptLen                        = 0;
        MacCommandsBufferIndex         = 0;
        MacCommandsBufferToRepeatIndex = 0;
    }

    // Verify if the fOpts and the payload fit into the maximum payload
    if (ValidatePayloadLength(size, datarate, fOptLen) == false) {
        return LORAMAC_STATUS_LENGTH_ERROR;
    }
    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMibGetRequestConfirm(MibRequestConfirm_t *mibGet)
{
    LoRaMacStatus_t status = LORAMAC_STATUS_OK;
    GetPhyParams_t  getPhy;
    PhyParam_t      phyParam;

    if (mibGet == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    switch (mibGet->Type) {
        case MIB_DEVICE_CLASS: {
            mibGet->Param.Class = LoRaMacDeviceClass;
            break;
        }
        case MIB_NETWORK_JOINED: {
            mibGet->Param.IsNetworkJoined = IsLoRaMacNetworkJoined;
            break;
        }
        case MIB_ADR: {
            mibGet->Param.AdrEnable = AdrCtrlOn;
            break;
        }
        case MIB_NET_ID: {
            mibGet->Param.NetID = LoRaMacNetID;
            break;
        }
        case MIB_DEV_ADDR: {
            mibGet->Param.DevAddr = LoRaMacDevAddr;
            break;
        }
        case MIB_NWK_SKEY: {
            mibGet->Param.NwkSKey = LoRaMacNwkSKey;
            break;
        }
        case MIB_APP_SKEY: {
            mibGet->Param.AppSKey = LoRaMacAppSKey;
            break;
        }
        case MIB_PUBLIC_NETWORK: {
            mibGet->Param.EnablePublicNetwork = PublicNetwork;
            break;
        }
        case MIB_REPEATER_SUPPORT: {
            mibGet->Param.EnableRepeaterSupport = RepeaterSupport;
            break;
        }
        case MIB_CHANNELS: {
            getPhy.Attribute = PHY_CHANNELS;
            phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);

            mibGet->Param.ChannelList = phyParam.Channels;
            break;
        }
        case MIB_RX1_DROFFSET: {
            mibGet->Param.Rx1DrOffset = LoRaMacParams.Rx1DrOffset;
            break;
        }
        case MIB_RX2_CHANNEL: {
            mibGet->Param.Rx2Channel = LoRaMacParams.Rx2Channel;
            break;
        }
        case MIB_RX2_DEFAULT_CHANNEL: {
            mibGet->Param.Rx2Channel = LoRaMacParamsDefaults.Rx2Channel;
            break;
        }
        case MIB_CHANNELS_DEFAULT_MASK: {
            getPhy.Attribute = PHY_CHANNELS_DEFAULT_MASK;
            phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);

            mibGet->Param.ChannelsDefaultMask = phyParam.ChannelsMask;
            break;
        }
        case MIB_CHANNELS_MASK: {
            getPhy.Attribute = PHY_CHANNELS_MASK;
            phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);

            mibGet->Param.ChannelsMask = phyParam.ChannelsMask;
            break;
        }
        case MIB_CHANNELS_NB_REP: {
            mibGet->Param.ChannelNbRep = LoRaMacParams.ChannelsNbRep;
            break;
        }
        case MIB_MAX_RX_WINDOW_DURATION: {
            mibGet->Param.MaxRxWindow = LoRaMacParams.MaxRxWindow;
            break;
        }
        case MIB_RECEIVE_DELAY_1: {
            mibGet->Param.ReceiveDelay1 = LoRaMacParams.ReceiveDelay1;
            break;
        }
        case MIB_RECEIVE_DELAY_2: {
            mibGet->Param.ReceiveDelay2 = LoRaMacParams.ReceiveDelay2;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_1: {
            mibGet->Param.JoinAcceptDelay1 = LoRaMacParams.JoinAcceptDelay1;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_2: {
            mibGet->Param.JoinAcceptDelay2 = LoRaMacParams.JoinAcceptDelay2;
            break;
        }
        case MIB_CHANNELS_DEFAULT_DATARATE: {
            mibGet->Param.ChannelsDefaultDatarate =
              LoRaMacParamsDefaults.ChannelsDatarate;
            break;
        }
        case MIB_CHANNELS_DATARATE: {
            mibGet->Param.ChannelsDatarate = LoRaMacParams.ChannelsDatarate;
            break;
        }
        case MIB_CHANNELS_DEFAULT_TX_POWER: {
            mibGet->Param.ChannelsDefaultTxPower =
              LoRaMacParamsDefaults.ChannelsTxPower;
            break;
        }
        case MIB_CHANNELS_TX_POWER: {
            mibGet->Param.ChannelsTxPower = LoRaMacParams.ChannelsTxPower;
            break;
        }
        case MIB_UPLINK_COUNTER: {
            mibGet->Param.UpLinkCounter = UpLinkCounter;
            break;
        }
        case MIB_DOWNLINK_COUNTER: {
            mibGet->Param.DownLinkCounter = DownLinkCounter;
            break;
        }
        case MIB_MULTICAST_CHANNEL: {
            mibGet->Param.MulticastList = MulticastChannels;
            break;
        }
        case MIB_SYSTEM_MAX_RX_ERROR: {
            mibGet->Param.SystemMaxRxError = LoRaMacParams.SystemMaxRxError;
            break;
        }
        case MIB_MIN_RX_SYMBOLS: {
            mibGet->Param.MinRxSymbols = LoRaMacParams.MinRxSymbols;
            break;
        }
        case MIB_ANTENNA_GAIN: {
            mibGet->Param.AntennaGain = LoRaMacParams.AntennaGain;
            break;
        }
#ifdef CONFIG_LINKWAN
        case MIB_FREQ_BAND: {
            mibGet->Param.freqband = LoRaMacParams.freqband;
            break;
        }
#endif
        default:
            status = LORAMAC_STATUS_SERVICE_UNKNOWN;
            break;
    }

    return status;
}

LoRaMacStatus_t LoRaMacMibSetRequestConfirm(MibRequestConfirm_t *mibSet)
{
    LoRaMacStatus_t     status = LORAMAC_STATUS_OK;
    ChanMaskSetParams_t chanMaskSet;
    VerifyParams_t      verify;

    if (mibSet == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        return LORAMAC_STATUS_BUSY;
    }

    switch (mibSet->Type) {
        case MIB_DEVICE_CLASS: {
            LoRaMacDeviceClass = mibSet->Param.Class;
            switch (LoRaMacDeviceClass) {
                case CLASS_A: {
                    // Set the radio into sleep to setup a defined state
                    Radio.Sleep();
                    break;
                }
                case CLASS_B: {
                    break;
                }
                case CLASS_C: {
                    // Set the NodeAckRequested indicator to default
                    NodeAckRequested = false;
                    OnRxWindow2TimerEvent();
                    break;
                }
            }
            break;
        }
        case MIB_NETWORK_JOINED: {
            IsLoRaMacNetworkJoined = mibSet->Param.IsNetworkJoined;
            break;
        }
        case MIB_ADR: {
            AdrCtrlOn = mibSet->Param.AdrEnable;
            break;
        }
        case MIB_NET_ID: {
            LoRaMacNetID = mibSet->Param.NetID;
            break;
        }
        case MIB_DEV_ADDR: {
            LoRaMacDevAddr = mibSet->Param.DevAddr;
            break;
        }
        case MIB_NWK_SKEY: {
            if (mibSet->Param.NwkSKey != NULL) {
                memcpy1(LoRaMacNwkSKey, mibSet->Param.NwkSKey,
                        sizeof(LoRaMacNwkSKey));
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_APP_SKEY: {
            if (mibSet->Param.AppSKey != NULL) {
                memcpy1(LoRaMacAppSKey, mibSet->Param.AppSKey,
                        sizeof(LoRaMacAppSKey));
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_PUBLIC_NETWORK: {
            PublicNetwork = mibSet->Param.EnablePublicNetwork;
            Radio.SetPublicNetwork(PublicNetwork);
            break;
        }
        case MIB_REPEATER_SUPPORT: {
            RepeaterSupport = mibSet->Param.EnableRepeaterSupport;
            break;
        }
        case MIB_RX1_DROFFSET: {
            LoRaMacParams.Rx1DrOffset = mibSet->Param.Rx1DrOffset;
            break;
        }
        case MIB_RX2_CHANNEL: {
            verify.DatarateParams.Datarate = mibSet->Param.Rx2Channel.Datarate;
            verify.DatarateParams.DownlinkDwellTime =
              LoRaMacParams.DownlinkDwellTime;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_RX_DR) == true) {
                LoRaMacParams.Rx2Channel = mibSet->Param.Rx2Channel;

                if ((LoRaMacDeviceClass == CLASS_C) &&
                    (IsLoRaMacNetworkJoined == true)) {
                    // Compute Rx2 windows parameters
                    RegionComputeRxWindowParameters(
                      LoRaMacRegion, LoRaMacParams.Rx2Channel.Datarate,
                      LoRaMacParams.MinRxSymbols,
                      LoRaMacParams.SystemMaxRxError, &RxWindow2Config);

                    RxWindow2Config.Channel = Channel;
                    RxWindow2Config.Frequency =
                      LoRaMacParams.Rx2Channel.Frequency;
                    RxWindow2Config.DownlinkDwellTime =
                      LoRaMacParams.DownlinkDwellTime;
                    RxWindow2Config.RepeaterSupport = RepeaterSupport;
                    RxWindow2Config.Window          = 1;
                    RxWindow2Config.RxContinuous    = true;

                    if (RegionRxConfig(LoRaMacRegion, &RxWindow2Config,
                                       (int8_t *)&McpsIndication.RxDatarate) ==
                        true) {
                        RxWindowSetup(RxWindow2Config.RxContinuous,
                                      LoRaMacParams.MaxRxWindow);
                        RxSlot = RxWindow2Config.Window;
                    } else {
                        status = LORAMAC_STATUS_PARAMETER_INVALID;
                    }
                }
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_RX2_DEFAULT_CHANNEL: {
            verify.DatarateParams.Datarate = mibSet->Param.Rx2Channel.Datarate;
            verify.DatarateParams.DownlinkDwellTime =
              LoRaMacParams.DownlinkDwellTime;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_RX_DR) == true) {
                LoRaMacParamsDefaults.Rx2Channel =
                  mibSet->Param.Rx2DefaultChannel;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_DEFAULT_MASK: {
            chanMaskSet.ChannelsMaskIn   = mibSet->Param.ChannelsMask;
            chanMaskSet.ChannelsMaskType = CHANNELS_DEFAULT_MASK;

            if (RegionChanMaskSet(LoRaMacRegion, &chanMaskSet) == false) {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_MASK: {
            chanMaskSet.ChannelsMaskIn   = mibSet->Param.ChannelsMask;
            chanMaskSet.ChannelsMaskType = CHANNELS_MASK;

            if (RegionChanMaskSet(LoRaMacRegion, &chanMaskSet) == false) {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_NB_REP: {
            if ((mibSet->Param.ChannelNbRep >= 1) &&
                (mibSet->Param.ChannelNbRep <= 15)) {
                LoRaMacParams.ChannelsNbRep = mibSet->Param.ChannelNbRep;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_MAX_RX_WINDOW_DURATION: {
            LoRaMacParams.MaxRxWindow = mibSet->Param.MaxRxWindow;
            break;
        }
        case MIB_RECEIVE_DELAY_1: {
            LoRaMacParams.ReceiveDelay1 = mibSet->Param.ReceiveDelay1;
            break;
        }
        case MIB_RECEIVE_DELAY_2: {
            LoRaMacParams.ReceiveDelay2 = mibSet->Param.ReceiveDelay2;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_1: {
            LoRaMacParams.JoinAcceptDelay1 = mibSet->Param.JoinAcceptDelay1;
            break;
        }
        case MIB_JOIN_ACCEPT_DELAY_2: {
            LoRaMacParams.JoinAcceptDelay2 = mibSet->Param.JoinAcceptDelay2;
            break;
        }
        case MIB_CHANNELS_DEFAULT_DATARATE: {
            verify.DatarateParams.Datarate =
              mibSet->Param.ChannelsDefaultDatarate;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_DEF_TX_DR) == true) {
                LoRaMacParamsDefaults.ChannelsDatarate =
                  verify.DatarateParams.Datarate;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_DATARATE: {
            verify.DatarateParams.Datarate = mibSet->Param.ChannelsDatarate;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_TX_DR) == true) {
                LoRaMacParams.ChannelsDatarate = verify.DatarateParams.Datarate;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_DEFAULT_TX_POWER: {
            verify.TxPower = mibSet->Param.ChannelsDefaultTxPower;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_DEF_TX_POWER) ==
                true) {
                LoRaMacParamsDefaults.ChannelsTxPower = verify.TxPower;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_TX_POWER: {
            verify.TxPower = mibSet->Param.ChannelsTxPower;
            if (RegionVerify(LoRaMacRegion, &verify, PHY_TX_POWER) == true) {
                LoRaMacParams.ChannelsTxPower = verify.TxPower;
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_UPLINK_COUNTER: {
            UpLinkCounter = mibSet->Param.UpLinkCounter;
            break;
        }
        case MIB_DOWNLINK_COUNTER: {
            DownLinkCounter = mibSet->Param.DownLinkCounter;
            break;
        }
        case MIB_SYSTEM_MAX_RX_ERROR: {
            LoRaMacParams.SystemMaxRxError =
              LoRaMacParamsDefaults.SystemMaxRxError =
                mibSet->Param.SystemMaxRxError;
            break;
        }
        case MIB_MIN_RX_SYMBOLS: {
            LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols =
              mibSet->Param.MinRxSymbols;
            break;
        }
        case MIB_ANTENNA_GAIN: {
            LoRaMacParams.AntennaGain = mibSet->Param.AntennaGain;
            break;
        }
        case MIB_MULTICAST_CHANNEL: {
            if (mibSet->Param.MulticastList != NULL) {
                status =
                  LoRaMacMulticastChannelLink(mibSet->Param.MulticastList);
            } else {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        default:
            status = LORAMAC_STATUS_SERVICE_UNKNOWN;
            break;
    }

    return status;
}

LoRaMacStatus_t LoRaMacChannelAdd(uint8_t id, ChannelParams_t params)
{
    ChannelAddParams_t channelAdd;

    // Validate if the MAC is in a correct state
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        if ((LoRaMacState & LORAMAC_TX_CONFIG) != LORAMAC_TX_CONFIG) {
            return LORAMAC_STATUS_BUSY;
        }
    }

    channelAdd.NewChannel = &params;
    channelAdd.ChannelId  = id;

    return RegionChannelAdd(LoRaMacRegion, &channelAdd);
}

LoRaMacStatus_t LoRaMacChannelRemove(uint8_t id)
{
    ChannelRemoveParams_t channelRemove;

    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        if ((LoRaMacState & LORAMAC_TX_CONFIG) != LORAMAC_TX_CONFIG) {
            return LORAMAC_STATUS_BUSY;
        }
    }

    channelRemove.ChannelId = id;

    if (RegionChannelsRemove(LoRaMacRegion, &channelRemove) == false) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMulticastChannelLink(MulticastParams_t *channelParam)
{
    if (channelParam == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        return LORAMAC_STATUS_BUSY;
    }

    // Reset downlink counter
    channelParam->DownLinkCounter = 0;

    if (MulticastChannels == NULL) {
        // New node is the fist element
        MulticastChannels = channelParam;
    } else {
        MulticastParams_t *cur = MulticastChannels;

        // Search the last node in the list
        while (cur->Next != NULL) {
            cur = cur->Next;
        }
        // This function always finds the last node
        cur->Next = channelParam;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMulticastChannelUnlink(MulticastParams_t *channelParam)
{
    if (channelParam == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        return LORAMAC_STATUS_BUSY;
    }

    if (MulticastChannels != NULL) {
        if (MulticastChannels == channelParam) {
            // First element
            MulticastChannels = channelParam->Next;
        } else {
            MulticastParams_t *cur = MulticastChannels;

            // Search the node in the list
            while (cur->Next && cur->Next != channelParam) {
                cur = cur->Next;
            }
            // If we found the node, remove it
            if (cur->Next) {
                cur->Next = channelParam->Next;
            }
        }
        channelParam->Next = NULL;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMlmeRequest(MlmeReq_t *mlmeRequest)
{
    LoRaMacStatus_t     status = LORAMAC_STATUS_SERVICE_UNKNOWN;
    LoRaMacHeader_t     macHdr;
    AlternateDrParams_t altDr;
    VerifyParams_t      verify;
    GetPhyParams_t      getPhy;
    PhyParam_t          phyParam;

    if (mlmeRequest == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) {
        return LORAMAC_STATUS_BUSY;
    }

    memset1((uint8_t *)&MlmeConfirm, 0, sizeof(MlmeConfirm));

    MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;

    switch (mlmeRequest->Type) {
        case MLME_JOIN: {
            if ((LoRaMacState & LORAMAC_TX_DELAYED) == LORAMAC_TX_DELAYED) {
                return LORAMAC_STATUS_BUSY;
            }

            if ((mlmeRequest->Req.Join.DevEui == NULL) ||
                (mlmeRequest->Req.Join.AppEui == NULL) ||
                (mlmeRequest->Req.Join.AppKey == NULL) ||
                (mlmeRequest->Req.Join.NbTrials == 0)) {
                return LORAMAC_STATUS_PARAMETER_INVALID;
            }

            // Verify the parameter NbTrials for the join procedure
            verify.NbJoinTrials = mlmeRequest->Req.Join.NbTrials;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_NB_JOIN_TRIALS) ==
                false) {
                // Value not supported, get default
                getPhy.Attribute = PHY_DEF_NB_JOIN_TRIALS;
                phyParam         = RegionGetPhyParam(LoRaMacRegion, &getPhy);
                mlmeRequest->Req.Join.NbTrials = (uint8_t)phyParam.Value;
            }

            LoRaMacFlags.Bits.MlmeReq = 1;
            MlmeConfirm.MlmeRequest   = mlmeRequest->Type;

            LoRaMacDevEui        = mlmeRequest->Req.Join.DevEui;
            LoRaMacAppEui        = mlmeRequest->Req.Join.AppEui;
            LoRaMacAppKey        = mlmeRequest->Req.Join.AppKey;
            MaxJoinRequestTrials = mlmeRequest->Req.Join.NbTrials;

            // Reset variable JoinRequestTrials
            JoinRequestTrials = 0;

            // Setup header information
            macHdr.Value      = 0;
            macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ;

            ResetMacParameters();

            altDr.NbTrials = JoinRequestTrials + 1;
#ifdef CONFIG_LINKWAN
            altDr.joinmethod              = mlmeRequest->Req.Join.method;
            altDr.datarate                = mlmeRequest->Req.Join.datarate;
            LoRaMacParams.method          = altDr.joinmethod;
            LoRaMacParams.freqband        = mlmeRequest->Req.Join.freqband;
            LoRaMacParams.update_freqband = true;
#endif
            LoRaMacParams.ChannelsDatarate =
              RegionAlternateDr(LoRaMacRegion, &altDr);

            status = Send(&macHdr, 0, NULL, 0);
            break;
        }
        case MLME_LINK_CHECK: {
            LoRaMacFlags.Bits.MlmeReq = 1;
            // LoRaMac will send this command piggy-pack
            MlmeConfirm.MlmeRequest = mlmeRequest->Type;

            status = AddMacCommand(MOTE_MAC_LINK_CHECK_REQ, 0, 0);
            break;
        }
        case MLME_TXCW: {
            MlmeConfirm.MlmeRequest   = mlmeRequest->Type;
            LoRaMacFlags.Bits.MlmeReq = 1;
            status = SetTxContinuousWave(mlmeRequest->Req.TxCw.Timeout);
            break;
        }
        case MLME_TXCW_1: {
            MlmeConfirm.MlmeRequest   = mlmeRequest->Type;
            LoRaMacFlags.Bits.MlmeReq = 1;
            status = SetTxContinuousWave1(mlmeRequest->Req.TxCw.Timeout,
                                          mlmeRequest->Req.TxCw.Frequency,
                                          mlmeRequest->Req.TxCw.Power);
            break;
        }
        default:
            break;
    }

    if (status != LORAMAC_STATUS_OK) {
        NodeAckRequested          = false;
        LoRaMacFlags.Bits.MlmeReq = 0;
    }

    return status;
}

LoRaMacStatus_t LoRaMacMcpsRequest(McpsReq_t *mcpsRequest)
{
    GetPhyParams_t  getPhy;
    PhyParam_t      phyParam;
    LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN;
    LoRaMacHeader_t macHdr;
    VerifyParams_t  verify;
    uint8_t         fPort = 0;
    void           *fBuffer;
    uint16_t        fBufferSize;
    int8_t          datarate = 0;
    bool            readyToSend = false;

    if (mcpsRequest == NULL) {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }
    if (((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) ||
        ((LoRaMacState & LORAMAC_TX_DELAYED) == LORAMAC_TX_DELAYED)) {
        return LORAMAC_STATUS_BUSY;
    }

    macHdr.Value = 0;
    memset1((uint8_t *)&McpsConfirm, 0, sizeof(McpsConfirm));
    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;

    // AckTimeoutRetriesCounter must be reset every time a new request
    // (unconfirmed or confirmed) is performed.
    AckTimeoutRetriesCounter = 1;

    switch (mcpsRequest->Type) {
        case MCPS_UNCONFIRMED: {
            readyToSend       = true;
            AckTimeoutRetries = 1;

            macHdr.Bits.MType = FRAME_TYPE_DATA_UNCONFIRMED_UP;
            fPort             = mcpsRequest->Req.Unconfirmed.fPort;
            fBuffer           = mcpsRequest->Req.Unconfirmed.fBuffer;
            fBufferSize       = mcpsRequest->Req.Unconfirmed.fBufferSize;
            datarate          = mcpsRequest->Req.Unconfirmed.Datarate;
            break;
        }
        case MCPS_CONFIRMED: {
            readyToSend       = true;
            AckTimeoutRetries = mcpsRequest->Req.Confirmed.NbTrials;

            macHdr.Bits.MType = FRAME_TYPE_DATA_CONFIRMED_UP;
            fPort             = mcpsRequest->Req.Confirmed.fPort;
            fBuffer           = mcpsRequest->Req.Confirmed.fBuffer;
            fBufferSize       = mcpsRequest->Req.Confirmed.fBufferSize;
            datarate          = mcpsRequest->Req.Confirmed.Datarate;
            break;
        }
        case MCPS_PROPRIETARY: {
            readyToSend       = true;
            AckTimeoutRetries = 1;

            macHdr.Bits.MType = FRAME_TYPE_PROPRIETARY;
            fBuffer           = mcpsRequest->Req.Proprietary.fBuffer;
            fBufferSize       = mcpsRequest->Req.Proprietary.fBufferSize;
            datarate          = mcpsRequest->Req.Proprietary.Datarate;
            break;
        }
        default:
            break;
    }

    // Get the minimum possible datarate
    getPhy.Attribute       = PHY_MIN_TX_DR;
    getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime;
    phyParam               = RegionGetPhyParam(LoRaMacRegion, &getPhy);
    // Apply the minimum possible datarate.
    // Some regions have limitations for the minimum datarate.
    datarate = MAX(datarate, phyParam.Value);

    if (readyToSend == true) {
        if (AdrCtrlOn == false) {
            verify.DatarateParams.Datarate = datarate;
            verify.DatarateParams.UplinkDwellTime =
              LoRaMacParams.UplinkDwellTime;

            if (RegionVerify(LoRaMacRegion, &verify, PHY_TX_DR) == true) {
                LoRaMacParams.ChannelsDatarate = verify.DatarateParams.Datarate;
            } else {
                return LORAMAC_STATUS_PARAMETER_INVALID;
            }
        }

        status = Send(&macHdr, fPort, fBuffer, fBufferSize);
        if (status == LORAMAC_STATUS_OK) {
            McpsConfirm.McpsRequest   = mcpsRequest->Type;
            LoRaMacFlags.Bits.McpsReq = 1;
        } else {
            NodeAckRequested = false;
        }
    }

    return status;
}

void LoRaMacTestRxWindowsOn(bool enable)
{
    IsRxWindowsEnabled = enable;
}

void LoRaMacTestSetMic(uint16_t txPacketCounter)
{
    UpLinkCounter        = txPacketCounter;
    IsUpLinkCounterFixed = true;
}

void LoRaMacTestSetDutyCycleOn(bool enable)
{
    VerifyParams_t verify;

    verify.DutyCycle = enable;

    if (RegionVerify(LoRaMacRegion, &verify, PHY_DUTY_CYCLE) == true) {
        DutyCycleOn = enable;
    }
}

void LoRaMacTestSetChannel(uint8_t channel)
{
    Channel = channel;
}

void LoraMacDelMultCastNode(MulticastParams_t *multiCastNode)
{
    MulticastParams_t *multiCastCmp = MulticastChannels;
    MulticastParams_t *multiCastPre = NULL;

    if (multiCastNode == NULL) {
        return;
    }

    while (multiCastCmp != NULL) {
        if (multiCastNode == multiCastCmp) {
            if (multiCastPre) {
                multiCastPre->Next = multiCastCmp->Next;
            } else {
                MulticastChannels = MulticastChannels->Next;
            }

            free(multiCastNode);
            multiCastNode = NULL;

            return;
        }

        multiCastPre = multiCastCmp;
        multiCastCmp = multiCastCmp->Next;
    }
}

LoRaMacParams_t *get_lora_mac_params(void)
{
    return &(LoRaMacParams);
}
